##// END OF EJS Templates
meta-tags: fixes #4305, metatags are not taken into account when truncating output....
marcink -
r1102:e81bd352 default
parent child Browse files
Show More
@@ -1,1055 +1,1057 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 Repository model for rhodecode
22 Repository model for rhodecode
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 import shutil
28 import shutil
29 import time
29 import time
30 import traceback
30 import traceback
31 from datetime import datetime
31 from datetime import datetime
32
32
33 from sqlalchemy.sql import func
33 from sqlalchemy.sql import func
34 from sqlalchemy.sql.expression import true, or_
34 from sqlalchemy.sql.expression import true, or_
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36
36
37 from rhodecode import events
37 from rhodecode import events
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import HasUserGroupPermissionAny
39 from rhodecode.lib.auth import HasUserGroupPermissionAny
40 from rhodecode.lib.caching_query import FromCache
40 from rhodecode.lib.caching_query import FromCache
41 from rhodecode.lib.exceptions import AttachedForksError
41 from rhodecode.lib.exceptions import AttachedForksError
42 from rhodecode.lib.hooks_base import log_delete_repository
42 from rhodecode.lib.hooks_base import log_delete_repository
43 from rhodecode.lib.markup_renderer import MarkupRenderer
43 from rhodecode.lib.markup_renderer import MarkupRenderer
44 from rhodecode.lib.utils import make_db_config
44 from rhodecode.lib.utils import make_db_config
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
46 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
47 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
47 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
48 from rhodecode.lib.vcs.backends import get_backend
48 from rhodecode.lib.vcs.backends import get_backend
49 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
49 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
50 from rhodecode.model import BaseModel
50 from rhodecode.model import BaseModel
51 from rhodecode.model.db import (
51 from rhodecode.model.db import (
52 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
52 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
53 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
53 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
54 RepoGroup, RepositoryField)
54 RepoGroup, RepositoryField)
55 from rhodecode.model.scm import UserGroupList
55 from rhodecode.model.scm import UserGroupList
56 from rhodecode.model.settings import VcsSettingsModel
56 from rhodecode.model.settings import VcsSettingsModel
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 class RepoModel(BaseModel):
62 class RepoModel(BaseModel):
63
63
64 cls = Repository
64 cls = Repository
65
65
66 def _get_user_group(self, users_group):
66 def _get_user_group(self, users_group):
67 return self._get_instance(UserGroup, users_group,
67 return self._get_instance(UserGroup, users_group,
68 callback=UserGroup.get_by_group_name)
68 callback=UserGroup.get_by_group_name)
69
69
70 def _get_repo_group(self, repo_group):
70 def _get_repo_group(self, repo_group):
71 return self._get_instance(RepoGroup, repo_group,
71 return self._get_instance(RepoGroup, repo_group,
72 callback=RepoGroup.get_by_group_name)
72 callback=RepoGroup.get_by_group_name)
73
73
74 def _create_default_perms(self, repository, private):
74 def _create_default_perms(self, repository, private):
75 # create default permission
75 # create default permission
76 default = 'repository.read'
76 default = 'repository.read'
77 def_user = User.get_default_user()
77 def_user = User.get_default_user()
78 for p in def_user.user_perms:
78 for p in def_user.user_perms:
79 if p.permission.permission_name.startswith('repository.'):
79 if p.permission.permission_name.startswith('repository.'):
80 default = p.permission.permission_name
80 default = p.permission.permission_name
81 break
81 break
82
82
83 default_perm = 'repository.none' if private else default
83 default_perm = 'repository.none' if private else default
84
84
85 repo_to_perm = UserRepoToPerm()
85 repo_to_perm = UserRepoToPerm()
86 repo_to_perm.permission = Permission.get_by_key(default_perm)
86 repo_to_perm.permission = Permission.get_by_key(default_perm)
87
87
88 repo_to_perm.repository = repository
88 repo_to_perm.repository = repository
89 repo_to_perm.user_id = def_user.user_id
89 repo_to_perm.user_id = def_user.user_id
90
90
91 return repo_to_perm
91 return repo_to_perm
92
92
93 @LazyProperty
93 @LazyProperty
94 def repos_path(self):
94 def repos_path(self):
95 """
95 """
96 Gets the repositories root path from database
96 Gets the repositories root path from database
97 """
97 """
98 settings_model = VcsSettingsModel(sa=self.sa)
98 settings_model = VcsSettingsModel(sa=self.sa)
99 return settings_model.get_repos_location()
99 return settings_model.get_repos_location()
100
100
101 def get(self, repo_id, cache=False):
101 def get(self, repo_id, cache=False):
102 repo = self.sa.query(Repository) \
102 repo = self.sa.query(Repository) \
103 .filter(Repository.repo_id == repo_id)
103 .filter(Repository.repo_id == repo_id)
104
104
105 if cache:
105 if cache:
106 repo = repo.options(FromCache("sql_cache_short",
106 repo = repo.options(FromCache("sql_cache_short",
107 "get_repo_%s" % repo_id))
107 "get_repo_%s" % repo_id))
108 return repo.scalar()
108 return repo.scalar()
109
109
110 def get_repo(self, repository):
110 def get_repo(self, repository):
111 return self._get_repo(repository)
111 return self._get_repo(repository)
112
112
113 def get_by_repo_name(self, repo_name, cache=False):
113 def get_by_repo_name(self, repo_name, cache=False):
114 repo = self.sa.query(Repository) \
114 repo = self.sa.query(Repository) \
115 .filter(Repository.repo_name == repo_name)
115 .filter(Repository.repo_name == repo_name)
116
116
117 if cache:
117 if cache:
118 repo = repo.options(FromCache("sql_cache_short",
118 repo = repo.options(FromCache("sql_cache_short",
119 "get_repo_%s" % repo_name))
119 "get_repo_%s" % repo_name))
120 return repo.scalar()
120 return repo.scalar()
121
121
122 def _extract_id_from_repo_name(self, repo_name):
122 def _extract_id_from_repo_name(self, repo_name):
123 if repo_name.startswith('/'):
123 if repo_name.startswith('/'):
124 repo_name = repo_name.lstrip('/')
124 repo_name = repo_name.lstrip('/')
125 by_id_match = re.match(r'^_(\d{1,})', repo_name)
125 by_id_match = re.match(r'^_(\d{1,})', repo_name)
126 if by_id_match:
126 if by_id_match:
127 return by_id_match.groups()[0]
127 return by_id_match.groups()[0]
128
128
129 def get_repo_by_id(self, repo_name):
129 def get_repo_by_id(self, repo_name):
130 """
130 """
131 Extracts repo_name by id from special urls.
131 Extracts repo_name by id from special urls.
132 Example url is _11/repo_name
132 Example url is _11/repo_name
133
133
134 :param repo_name:
134 :param repo_name:
135 :return: repo object if matched else None
135 :return: repo object if matched else None
136 """
136 """
137 try:
137 try:
138 _repo_id = self._extract_id_from_repo_name(repo_name)
138 _repo_id = self._extract_id_from_repo_name(repo_name)
139 if _repo_id:
139 if _repo_id:
140 return self.get(_repo_id)
140 return self.get(_repo_id)
141 except Exception:
141 except Exception:
142 log.exception('Failed to extract repo_name from URL')
142 log.exception('Failed to extract repo_name from URL')
143
143
144 return None
144 return None
145
145
146 def get_url(self, repo):
146 def get_url(self, repo):
147 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
147 return h.url('summary_home', repo_name=safe_str(repo.repo_name),
148 qualified=True)
148 qualified=True)
149
149
150 def get_users(self, name_contains=None, limit=20, only_active=True):
150 def get_users(self, name_contains=None, limit=20, only_active=True):
151
151
152 # TODO: mikhail: move this method to the UserModel.
152 # TODO: mikhail: move this method to the UserModel.
153 query = self.sa.query(User)
153 query = self.sa.query(User)
154 if only_active:
154 if only_active:
155 query = query.filter(User.active == true())
155 query = query.filter(User.active == true())
156
156
157 if name_contains:
157 if name_contains:
158 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
158 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
159 query = query.filter(
159 query = query.filter(
160 or_(
160 or_(
161 User.name.ilike(ilike_expression),
161 User.name.ilike(ilike_expression),
162 User.lastname.ilike(ilike_expression),
162 User.lastname.ilike(ilike_expression),
163 User.username.ilike(ilike_expression)
163 User.username.ilike(ilike_expression)
164 )
164 )
165 )
165 )
166 query = query.limit(limit)
166 query = query.limit(limit)
167 users = query.all()
167 users = query.all()
168
168
169 _users = [
169 _users = [
170 {
170 {
171 'id': user.user_id,
171 'id': user.user_id,
172 'first_name': user.name,
172 'first_name': user.name,
173 'last_name': user.lastname,
173 'last_name': user.lastname,
174 'username': user.username,
174 'username': user.username,
175 'email': user.email,
175 'email': user.email,
176 'icon_link': h.gravatar_url(user.email, 30),
176 'icon_link': h.gravatar_url(user.email, 30),
177 'value_display': h.person(user),
177 'value_display': h.person(user),
178 'value': user.username,
178 'value': user.username,
179 'value_type': 'user',
179 'value_type': 'user',
180 'active': user.active,
180 'active': user.active,
181 }
181 }
182 for user in users
182 for user in users
183 ]
183 ]
184 return _users
184 return _users
185
185
186 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
186 def get_user_groups(self, name_contains=None, limit=20, only_active=True):
187 # TODO: mikhail: move this method to the UserGroupModel.
187 # TODO: mikhail: move this method to the UserGroupModel.
188 query = self.sa.query(UserGroup)
188 query = self.sa.query(UserGroup)
189 if only_active:
189 if only_active:
190 query = query.filter(UserGroup.users_group_active == true())
190 query = query.filter(UserGroup.users_group_active == true())
191
191
192 if name_contains:
192 if name_contains:
193 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
193 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
194 query = query.filter(
194 query = query.filter(
195 UserGroup.users_group_name.ilike(ilike_expression))\
195 UserGroup.users_group_name.ilike(ilike_expression))\
196 .order_by(func.length(UserGroup.users_group_name))\
196 .order_by(func.length(UserGroup.users_group_name))\
197 .order_by(UserGroup.users_group_name)
197 .order_by(UserGroup.users_group_name)
198
198
199 query = query.limit(limit)
199 query = query.limit(limit)
200 user_groups = query.all()
200 user_groups = query.all()
201 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
201 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
202 user_groups = UserGroupList(user_groups, perm_set=perm_set)
202 user_groups = UserGroupList(user_groups, perm_set=perm_set)
203
203
204 _groups = [
204 _groups = [
205 {
205 {
206 'id': group.users_group_id,
206 'id': group.users_group_id,
207 # TODO: marcink figure out a way to generate the url for the
207 # TODO: marcink figure out a way to generate the url for the
208 # icon
208 # icon
209 'icon_link': '',
209 'icon_link': '',
210 'value_display': 'Group: %s (%d members)' % (
210 'value_display': 'Group: %s (%d members)' % (
211 group.users_group_name, len(group.members),),
211 group.users_group_name, len(group.members),),
212 'value': group.users_group_name,
212 'value': group.users_group_name,
213 'value_type': 'user_group',
213 'value_type': 'user_group',
214 'active': group.users_group_active,
214 'active': group.users_group_active,
215 }
215 }
216 for group in user_groups
216 for group in user_groups
217 ]
217 ]
218 return _groups
218 return _groups
219
219
220 @classmethod
220 @classmethod
221 def update_repoinfo(cls, repositories=None):
221 def update_repoinfo(cls, repositories=None):
222 if not repositories:
222 if not repositories:
223 repositories = Repository.getAll()
223 repositories = Repository.getAll()
224 for repo in repositories:
224 for repo in repositories:
225 repo.update_commit_cache()
225 repo.update_commit_cache()
226
226
227 def get_repos_as_dict(self, repo_list=None, admin=False,
227 def get_repos_as_dict(self, repo_list=None, admin=False,
228 super_user_actions=False):
228 super_user_actions=False):
229
229
230 from rhodecode.lib.utils import PartialRenderer
230 from rhodecode.lib.utils import PartialRenderer
231 _render = PartialRenderer('data_table/_dt_elements.html')
231 _render = PartialRenderer('data_table/_dt_elements.html')
232 c = _render.c
232 c = _render.c
233
233
234 def quick_menu(repo_name):
234 def quick_menu(repo_name):
235 return _render('quick_menu', repo_name)
235 return _render('quick_menu', repo_name)
236
236
237 def repo_lnk(name, rtype, rstate, private, fork_of):
237 def repo_lnk(name, rtype, rstate, private, fork_of):
238 return _render('repo_name', name, rtype, rstate, private, fork_of,
238 return _render('repo_name', name, rtype, rstate, private, fork_of,
239 short_name=not admin, admin=False)
239 short_name=not admin, admin=False)
240
240
241 def last_change(last_change):
241 def last_change(last_change):
242 return _render("last_change", last_change)
242 return _render("last_change", last_change)
243
243
244 def rss_lnk(repo_name):
244 def rss_lnk(repo_name):
245 return _render("rss", repo_name)
245 return _render("rss", repo_name)
246
246
247 def atom_lnk(repo_name):
247 def atom_lnk(repo_name):
248 return _render("atom", repo_name)
248 return _render("atom", repo_name)
249
249
250 def last_rev(repo_name, cs_cache):
250 def last_rev(repo_name, cs_cache):
251 return _render('revision', repo_name, cs_cache.get('revision'),
251 return _render('revision', repo_name, cs_cache.get('revision'),
252 cs_cache.get('raw_id'), cs_cache.get('author'),
252 cs_cache.get('raw_id'), cs_cache.get('author'),
253 cs_cache.get('message'))
253 cs_cache.get('message'))
254
254
255 def desc(desc):
255 def desc(desc):
256 if c.visual.stylify_metatags:
256 if c.visual.stylify_metatags:
257 return h.urlify_text(h.escaped_stylize(h.truncate(desc, 60)))
257 desc = h.urlify_text(h.escaped_stylize(desc))
258 else:
258 else:
259 return h.urlify_text(h.html_escape(h.truncate(desc, 60)))
259 desc = h.urlify_text(h.html_escape(desc))
260
261 return _render('repo_desc', desc)
260
262
261 def state(repo_state):
263 def state(repo_state):
262 return _render("repo_state", repo_state)
264 return _render("repo_state", repo_state)
263
265
264 def repo_actions(repo_name):
266 def repo_actions(repo_name):
265 return _render('repo_actions', repo_name, super_user_actions)
267 return _render('repo_actions', repo_name, super_user_actions)
266
268
267 def user_profile(username):
269 def user_profile(username):
268 return _render('user_profile', username)
270 return _render('user_profile', username)
269
271
270 repos_data = []
272 repos_data = []
271 for repo in repo_list:
273 for repo in repo_list:
272 cs_cache = repo.changeset_cache
274 cs_cache = repo.changeset_cache
273 row = {
275 row = {
274 "menu": quick_menu(repo.repo_name),
276 "menu": quick_menu(repo.repo_name),
275
277
276 "name": repo_lnk(repo.repo_name, repo.repo_type,
278 "name": repo_lnk(repo.repo_name, repo.repo_type,
277 repo.repo_state, repo.private, repo.fork),
279 repo.repo_state, repo.private, repo.fork),
278 "name_raw": repo.repo_name.lower(),
280 "name_raw": repo.repo_name.lower(),
279
281
280 "last_change": last_change(repo.last_db_change),
282 "last_change": last_change(repo.last_db_change),
281 "last_change_raw": datetime_to_time(repo.last_db_change),
283 "last_change_raw": datetime_to_time(repo.last_db_change),
282
284
283 "last_changeset": last_rev(repo.repo_name, cs_cache),
285 "last_changeset": last_rev(repo.repo_name, cs_cache),
284 "last_changeset_raw": cs_cache.get('revision'),
286 "last_changeset_raw": cs_cache.get('revision'),
285
287
286 "desc": desc(repo.description),
288 "desc": desc(repo.description),
287 "owner": user_profile(repo.user.username),
289 "owner": user_profile(repo.user.username),
288
290
289 "state": state(repo.repo_state),
291 "state": state(repo.repo_state),
290 "rss": rss_lnk(repo.repo_name),
292 "rss": rss_lnk(repo.repo_name),
291
293
292 "atom": atom_lnk(repo.repo_name),
294 "atom": atom_lnk(repo.repo_name),
293 }
295 }
294 if admin:
296 if admin:
295 row.update({
297 row.update({
296 "action": repo_actions(repo.repo_name),
298 "action": repo_actions(repo.repo_name),
297 })
299 })
298 repos_data.append(row)
300 repos_data.append(row)
299
301
300 return repos_data
302 return repos_data
301
303
302 def _get_defaults(self, repo_name):
304 def _get_defaults(self, repo_name):
303 """
305 """
304 Gets information about repository, and returns a dict for
306 Gets information about repository, and returns a dict for
305 usage in forms
307 usage in forms
306
308
307 :param repo_name:
309 :param repo_name:
308 """
310 """
309
311
310 repo_info = Repository.get_by_repo_name(repo_name)
312 repo_info = Repository.get_by_repo_name(repo_name)
311
313
312 if repo_info is None:
314 if repo_info is None:
313 return None
315 return None
314
316
315 defaults = repo_info.get_dict()
317 defaults = repo_info.get_dict()
316 defaults['repo_name'] = repo_info.just_name
318 defaults['repo_name'] = repo_info.just_name
317
319
318 groups = repo_info.groups_with_parents
320 groups = repo_info.groups_with_parents
319 parent_group = groups[-1] if groups else None
321 parent_group = groups[-1] if groups else None
320
322
321 # we use -1 as this is how in HTML, we mark an empty group
323 # we use -1 as this is how in HTML, we mark an empty group
322 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
324 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
323
325
324 keys_to_process = (
326 keys_to_process = (
325 {'k': 'repo_type', 'strip': False},
327 {'k': 'repo_type', 'strip': False},
326 {'k': 'repo_enable_downloads', 'strip': True},
328 {'k': 'repo_enable_downloads', 'strip': True},
327 {'k': 'repo_description', 'strip': True},
329 {'k': 'repo_description', 'strip': True},
328 {'k': 'repo_enable_locking', 'strip': True},
330 {'k': 'repo_enable_locking', 'strip': True},
329 {'k': 'repo_landing_rev', 'strip': True},
331 {'k': 'repo_landing_rev', 'strip': True},
330 {'k': 'clone_uri', 'strip': False},
332 {'k': 'clone_uri', 'strip': False},
331 {'k': 'repo_private', 'strip': True},
333 {'k': 'repo_private', 'strip': True},
332 {'k': 'repo_enable_statistics', 'strip': True}
334 {'k': 'repo_enable_statistics', 'strip': True}
333 )
335 )
334
336
335 for item in keys_to_process:
337 for item in keys_to_process:
336 attr = item['k']
338 attr = item['k']
337 if item['strip']:
339 if item['strip']:
338 attr = remove_prefix(item['k'], 'repo_')
340 attr = remove_prefix(item['k'], 'repo_')
339
341
340 val = defaults[attr]
342 val = defaults[attr]
341 if item['k'] == 'repo_landing_rev':
343 if item['k'] == 'repo_landing_rev':
342 val = ':'.join(defaults[attr])
344 val = ':'.join(defaults[attr])
343 defaults[item['k']] = val
345 defaults[item['k']] = val
344 if item['k'] == 'clone_uri':
346 if item['k'] == 'clone_uri':
345 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
347 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
346
348
347 # fill owner
349 # fill owner
348 if repo_info.user:
350 if repo_info.user:
349 defaults.update({'user': repo_info.user.username})
351 defaults.update({'user': repo_info.user.username})
350 else:
352 else:
351 replacement_user = User.get_first_super_admin().username
353 replacement_user = User.get_first_super_admin().username
352 defaults.update({'user': replacement_user})
354 defaults.update({'user': replacement_user})
353
355
354 # fill repository users
356 # fill repository users
355 for p in repo_info.repo_to_perm:
357 for p in repo_info.repo_to_perm:
356 defaults.update({'u_perm_%s' % p.user.user_id:
358 defaults.update({'u_perm_%s' % p.user.user_id:
357 p.permission.permission_name})
359 p.permission.permission_name})
358
360
359 # fill repository groups
361 # fill repository groups
360 for p in repo_info.users_group_to_perm:
362 for p in repo_info.users_group_to_perm:
361 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
363 defaults.update({'g_perm_%s' % p.users_group.users_group_id:
362 p.permission.permission_name})
364 p.permission.permission_name})
363
365
364 return defaults
366 return defaults
365
367
366 def update(self, repo, **kwargs):
368 def update(self, repo, **kwargs):
367 try:
369 try:
368 cur_repo = self._get_repo(repo)
370 cur_repo = self._get_repo(repo)
369 source_repo_name = cur_repo.repo_name
371 source_repo_name = cur_repo.repo_name
370 if 'user' in kwargs:
372 if 'user' in kwargs:
371 cur_repo.user = User.get_by_username(kwargs['user'])
373 cur_repo.user = User.get_by_username(kwargs['user'])
372
374
373 if 'repo_group' in kwargs:
375 if 'repo_group' in kwargs:
374 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
376 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
375 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
377 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
376
378
377 update_keys = [
379 update_keys = [
378 (1, 'repo_enable_downloads'),
380 (1, 'repo_enable_downloads'),
379 (1, 'repo_description'),
381 (1, 'repo_description'),
380 (1, 'repo_enable_locking'),
382 (1, 'repo_enable_locking'),
381 (1, 'repo_landing_rev'),
383 (1, 'repo_landing_rev'),
382 (1, 'repo_private'),
384 (1, 'repo_private'),
383 (1, 'repo_enable_statistics'),
385 (1, 'repo_enable_statistics'),
384 (0, 'clone_uri'),
386 (0, 'clone_uri'),
385 (0, 'fork_id')
387 (0, 'fork_id')
386 ]
388 ]
387 for strip, k in update_keys:
389 for strip, k in update_keys:
388 if k in kwargs:
390 if k in kwargs:
389 val = kwargs[k]
391 val = kwargs[k]
390 if strip:
392 if strip:
391 k = remove_prefix(k, 'repo_')
393 k = remove_prefix(k, 'repo_')
392 if k == 'clone_uri':
394 if k == 'clone_uri':
393 from rhodecode.model.validators import Missing
395 from rhodecode.model.validators import Missing
394 _change = kwargs.get('clone_uri_change')
396 _change = kwargs.get('clone_uri_change')
395 if _change in [Missing, 'OLD']:
397 if _change in [Missing, 'OLD']:
396 # we don't change the value, so use original one
398 # we don't change the value, so use original one
397 val = cur_repo.clone_uri
399 val = cur_repo.clone_uri
398
400
399 setattr(cur_repo, k, val)
401 setattr(cur_repo, k, val)
400
402
401 new_name = cur_repo.get_new_name(kwargs['repo_name'])
403 new_name = cur_repo.get_new_name(kwargs['repo_name'])
402 cur_repo.repo_name = new_name
404 cur_repo.repo_name = new_name
403
405
404 # if private flag is set, reset default permission to NONE
406 # if private flag is set, reset default permission to NONE
405 if kwargs.get('repo_private'):
407 if kwargs.get('repo_private'):
406 EMPTY_PERM = 'repository.none'
408 EMPTY_PERM = 'repository.none'
407 RepoModel().grant_user_permission(
409 RepoModel().grant_user_permission(
408 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
410 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
409 )
411 )
410
412
411 # handle extra fields
413 # handle extra fields
412 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
414 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
413 kwargs):
415 kwargs):
414 k = RepositoryField.un_prefix_key(field)
416 k = RepositoryField.un_prefix_key(field)
415 ex_field = RepositoryField.get_by_key_name(
417 ex_field = RepositoryField.get_by_key_name(
416 key=k, repo=cur_repo)
418 key=k, repo=cur_repo)
417 if ex_field:
419 if ex_field:
418 ex_field.field_value = kwargs[field]
420 ex_field.field_value = kwargs[field]
419 self.sa.add(ex_field)
421 self.sa.add(ex_field)
420 self.sa.add(cur_repo)
422 self.sa.add(cur_repo)
421
423
422 if source_repo_name != new_name:
424 if source_repo_name != new_name:
423 # rename repository
425 # rename repository
424 self._rename_filesystem_repo(
426 self._rename_filesystem_repo(
425 old=source_repo_name, new=new_name)
427 old=source_repo_name, new=new_name)
426
428
427 return cur_repo
429 return cur_repo
428 except Exception:
430 except Exception:
429 log.error(traceback.format_exc())
431 log.error(traceback.format_exc())
430 raise
432 raise
431
433
432 def _create_repo(self, repo_name, repo_type, description, owner,
434 def _create_repo(self, repo_name, repo_type, description, owner,
433 private=False, clone_uri=None, repo_group=None,
435 private=False, clone_uri=None, repo_group=None,
434 landing_rev='rev:tip', fork_of=None,
436 landing_rev='rev:tip', fork_of=None,
435 copy_fork_permissions=False, enable_statistics=False,
437 copy_fork_permissions=False, enable_statistics=False,
436 enable_locking=False, enable_downloads=False,
438 enable_locking=False, enable_downloads=False,
437 copy_group_permissions=False,
439 copy_group_permissions=False,
438 state=Repository.STATE_PENDING):
440 state=Repository.STATE_PENDING):
439 """
441 """
440 Create repository inside database with PENDING state, this should be
442 Create repository inside database with PENDING state, this should be
441 only executed by create() repo. With exception of importing existing
443 only executed by create() repo. With exception of importing existing
442 repos
444 repos
443 """
445 """
444 from rhodecode.model.scm import ScmModel
446 from rhodecode.model.scm import ScmModel
445
447
446 owner = self._get_user(owner)
448 owner = self._get_user(owner)
447 fork_of = self._get_repo(fork_of)
449 fork_of = self._get_repo(fork_of)
448 repo_group = self._get_repo_group(safe_int(repo_group))
450 repo_group = self._get_repo_group(safe_int(repo_group))
449
451
450 try:
452 try:
451 repo_name = safe_unicode(repo_name)
453 repo_name = safe_unicode(repo_name)
452 description = safe_unicode(description)
454 description = safe_unicode(description)
453 # repo name is just a name of repository
455 # repo name is just a name of repository
454 # while repo_name_full is a full qualified name that is combined
456 # while repo_name_full is a full qualified name that is combined
455 # with name and path of group
457 # with name and path of group
456 repo_name_full = repo_name
458 repo_name_full = repo_name
457 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
459 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
458
460
459 new_repo = Repository()
461 new_repo = Repository()
460 new_repo.repo_state = state
462 new_repo.repo_state = state
461 new_repo.enable_statistics = False
463 new_repo.enable_statistics = False
462 new_repo.repo_name = repo_name_full
464 new_repo.repo_name = repo_name_full
463 new_repo.repo_type = repo_type
465 new_repo.repo_type = repo_type
464 new_repo.user = owner
466 new_repo.user = owner
465 new_repo.group = repo_group
467 new_repo.group = repo_group
466 new_repo.description = description or repo_name
468 new_repo.description = description or repo_name
467 new_repo.private = private
469 new_repo.private = private
468 new_repo.clone_uri = clone_uri
470 new_repo.clone_uri = clone_uri
469 new_repo.landing_rev = landing_rev
471 new_repo.landing_rev = landing_rev
470
472
471 new_repo.enable_statistics = enable_statistics
473 new_repo.enable_statistics = enable_statistics
472 new_repo.enable_locking = enable_locking
474 new_repo.enable_locking = enable_locking
473 new_repo.enable_downloads = enable_downloads
475 new_repo.enable_downloads = enable_downloads
474
476
475 if repo_group:
477 if repo_group:
476 new_repo.enable_locking = repo_group.enable_locking
478 new_repo.enable_locking = repo_group.enable_locking
477
479
478 if fork_of:
480 if fork_of:
479 parent_repo = fork_of
481 parent_repo = fork_of
480 new_repo.fork = parent_repo
482 new_repo.fork = parent_repo
481
483
482 events.trigger(events.RepoPreCreateEvent(new_repo))
484 events.trigger(events.RepoPreCreateEvent(new_repo))
483
485
484 self.sa.add(new_repo)
486 self.sa.add(new_repo)
485
487
486 EMPTY_PERM = 'repository.none'
488 EMPTY_PERM = 'repository.none'
487 if fork_of and copy_fork_permissions:
489 if fork_of and copy_fork_permissions:
488 repo = fork_of
490 repo = fork_of
489 user_perms = UserRepoToPerm.query() \
491 user_perms = UserRepoToPerm.query() \
490 .filter(UserRepoToPerm.repository == repo).all()
492 .filter(UserRepoToPerm.repository == repo).all()
491 group_perms = UserGroupRepoToPerm.query() \
493 group_perms = UserGroupRepoToPerm.query() \
492 .filter(UserGroupRepoToPerm.repository == repo).all()
494 .filter(UserGroupRepoToPerm.repository == repo).all()
493
495
494 for perm in user_perms:
496 for perm in user_perms:
495 UserRepoToPerm.create(
497 UserRepoToPerm.create(
496 perm.user, new_repo, perm.permission)
498 perm.user, new_repo, perm.permission)
497
499
498 for perm in group_perms:
500 for perm in group_perms:
499 UserGroupRepoToPerm.create(
501 UserGroupRepoToPerm.create(
500 perm.users_group, new_repo, perm.permission)
502 perm.users_group, new_repo, perm.permission)
501 # in case we copy permissions and also set this repo to private
503 # in case we copy permissions and also set this repo to private
502 # override the default user permission to make it a private
504 # override the default user permission to make it a private
503 # repo
505 # repo
504 if private:
506 if private:
505 RepoModel(self.sa).grant_user_permission(
507 RepoModel(self.sa).grant_user_permission(
506 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
508 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
507
509
508 elif repo_group and copy_group_permissions:
510 elif repo_group and copy_group_permissions:
509 user_perms = UserRepoGroupToPerm.query() \
511 user_perms = UserRepoGroupToPerm.query() \
510 .filter(UserRepoGroupToPerm.group == repo_group).all()
512 .filter(UserRepoGroupToPerm.group == repo_group).all()
511
513
512 group_perms = UserGroupRepoGroupToPerm.query() \
514 group_perms = UserGroupRepoGroupToPerm.query() \
513 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
515 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
514
516
515 for perm in user_perms:
517 for perm in user_perms:
516 perm_name = perm.permission.permission_name.replace(
518 perm_name = perm.permission.permission_name.replace(
517 'group.', 'repository.')
519 'group.', 'repository.')
518 perm_obj = Permission.get_by_key(perm_name)
520 perm_obj = Permission.get_by_key(perm_name)
519 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
521 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
520
522
521 for perm in group_perms:
523 for perm in group_perms:
522 perm_name = perm.permission.permission_name.replace(
524 perm_name = perm.permission.permission_name.replace(
523 'group.', 'repository.')
525 'group.', 'repository.')
524 perm_obj = Permission.get_by_key(perm_name)
526 perm_obj = Permission.get_by_key(perm_name)
525 UserGroupRepoToPerm.create(
527 UserGroupRepoToPerm.create(
526 perm.users_group, new_repo, perm_obj)
528 perm.users_group, new_repo, perm_obj)
527
529
528 if private:
530 if private:
529 RepoModel(self.sa).grant_user_permission(
531 RepoModel(self.sa).grant_user_permission(
530 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
532 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
531
533
532 else:
534 else:
533 perm_obj = self._create_default_perms(new_repo, private)
535 perm_obj = self._create_default_perms(new_repo, private)
534 self.sa.add(perm_obj)
536 self.sa.add(perm_obj)
535
537
536 # now automatically start following this repository as owner
538 # now automatically start following this repository as owner
537 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
539 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
538 owner.user_id)
540 owner.user_id)
539
541
540 # we need to flush here, in order to check if database won't
542 # we need to flush here, in order to check if database won't
541 # throw any exceptions, create filesystem dirs at the very end
543 # throw any exceptions, create filesystem dirs at the very end
542 self.sa.flush()
544 self.sa.flush()
543 events.trigger(events.RepoCreateEvent(new_repo))
545 events.trigger(events.RepoCreateEvent(new_repo))
544 return new_repo
546 return new_repo
545
547
546 except Exception:
548 except Exception:
547 log.error(traceback.format_exc())
549 log.error(traceback.format_exc())
548 raise
550 raise
549
551
550 def create(self, form_data, cur_user):
552 def create(self, form_data, cur_user):
551 """
553 """
552 Create repository using celery tasks
554 Create repository using celery tasks
553
555
554 :param form_data:
556 :param form_data:
555 :param cur_user:
557 :param cur_user:
556 """
558 """
557 from rhodecode.lib.celerylib import tasks, run_task
559 from rhodecode.lib.celerylib import tasks, run_task
558 return run_task(tasks.create_repo, form_data, cur_user)
560 return run_task(tasks.create_repo, form_data, cur_user)
559
561
560 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
562 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
561 perm_deletions=None, check_perms=True,
563 perm_deletions=None, check_perms=True,
562 cur_user=None):
564 cur_user=None):
563 if not perm_additions:
565 if not perm_additions:
564 perm_additions = []
566 perm_additions = []
565 if not perm_updates:
567 if not perm_updates:
566 perm_updates = []
568 perm_updates = []
567 if not perm_deletions:
569 if not perm_deletions:
568 perm_deletions = []
570 perm_deletions = []
569
571
570 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
572 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
571
573
572 # update permissions
574 # update permissions
573 for member_id, perm, member_type in perm_updates:
575 for member_id, perm, member_type in perm_updates:
574 member_id = int(member_id)
576 member_id = int(member_id)
575 if member_type == 'user':
577 if member_type == 'user':
576 # this updates also current one if found
578 # this updates also current one if found
577 self.grant_user_permission(
579 self.grant_user_permission(
578 repo=repo, user=member_id, perm=perm)
580 repo=repo, user=member_id, perm=perm)
579 else: # set for user group
581 else: # set for user group
580 # check if we have permissions to alter this usergroup
582 # check if we have permissions to alter this usergroup
581 member_name = UserGroup.get(member_id).users_group_name
583 member_name = UserGroup.get(member_id).users_group_name
582 if not check_perms or HasUserGroupPermissionAny(
584 if not check_perms or HasUserGroupPermissionAny(
583 *req_perms)(member_name, user=cur_user):
585 *req_perms)(member_name, user=cur_user):
584 self.grant_user_group_permission(
586 self.grant_user_group_permission(
585 repo=repo, group_name=member_id, perm=perm)
587 repo=repo, group_name=member_id, perm=perm)
586
588
587 # set new permissions
589 # set new permissions
588 for member_id, perm, member_type in perm_additions:
590 for member_id, perm, member_type in perm_additions:
589 member_id = int(member_id)
591 member_id = int(member_id)
590 if member_type == 'user':
592 if member_type == 'user':
591 self.grant_user_permission(
593 self.grant_user_permission(
592 repo=repo, user=member_id, perm=perm)
594 repo=repo, user=member_id, perm=perm)
593 else: # set for user group
595 else: # set for user group
594 # check if we have permissions to alter this usergroup
596 # check if we have permissions to alter this usergroup
595 member_name = UserGroup.get(member_id).users_group_name
597 member_name = UserGroup.get(member_id).users_group_name
596 if not check_perms or HasUserGroupPermissionAny(
598 if not check_perms or HasUserGroupPermissionAny(
597 *req_perms)(member_name, user=cur_user):
599 *req_perms)(member_name, user=cur_user):
598 self.grant_user_group_permission(
600 self.grant_user_group_permission(
599 repo=repo, group_name=member_id, perm=perm)
601 repo=repo, group_name=member_id, perm=perm)
600
602
601 # delete permissions
603 # delete permissions
602 for member_id, perm, member_type in perm_deletions:
604 for member_id, perm, member_type in perm_deletions:
603 member_id = int(member_id)
605 member_id = int(member_id)
604 if member_type == 'user':
606 if member_type == 'user':
605 self.revoke_user_permission(repo=repo, user=member_id)
607 self.revoke_user_permission(repo=repo, user=member_id)
606 else: # set for user group
608 else: # set for user group
607 # check if we have permissions to alter this usergroup
609 # check if we have permissions to alter this usergroup
608 member_name = UserGroup.get(member_id).users_group_name
610 member_name = UserGroup.get(member_id).users_group_name
609 if not check_perms or HasUserGroupPermissionAny(
611 if not check_perms or HasUserGroupPermissionAny(
610 *req_perms)(member_name, user=cur_user):
612 *req_perms)(member_name, user=cur_user):
611 self.revoke_user_group_permission(
613 self.revoke_user_group_permission(
612 repo=repo, group_name=member_id)
614 repo=repo, group_name=member_id)
613
615
614 def create_fork(self, form_data, cur_user):
616 def create_fork(self, form_data, cur_user):
615 """
617 """
616 Simple wrapper into executing celery task for fork creation
618 Simple wrapper into executing celery task for fork creation
617
619
618 :param form_data:
620 :param form_data:
619 :param cur_user:
621 :param cur_user:
620 """
622 """
621 from rhodecode.lib.celerylib import tasks, run_task
623 from rhodecode.lib.celerylib import tasks, run_task
622 return run_task(tasks.create_repo_fork, form_data, cur_user)
624 return run_task(tasks.create_repo_fork, form_data, cur_user)
623
625
624 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
626 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
625 """
627 """
626 Delete given repository, forks parameter defines what do do with
628 Delete given repository, forks parameter defines what do do with
627 attached forks. Throws AttachedForksError if deleted repo has attached
629 attached forks. Throws AttachedForksError if deleted repo has attached
628 forks
630 forks
629
631
630 :param repo:
632 :param repo:
631 :param forks: str 'delete' or 'detach'
633 :param forks: str 'delete' or 'detach'
632 :param fs_remove: remove(archive) repo from filesystem
634 :param fs_remove: remove(archive) repo from filesystem
633 """
635 """
634 if not cur_user:
636 if not cur_user:
635 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
637 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
636 repo = self._get_repo(repo)
638 repo = self._get_repo(repo)
637 if repo:
639 if repo:
638 if forks == 'detach':
640 if forks == 'detach':
639 for r in repo.forks:
641 for r in repo.forks:
640 r.fork = None
642 r.fork = None
641 self.sa.add(r)
643 self.sa.add(r)
642 elif forks == 'delete':
644 elif forks == 'delete':
643 for r in repo.forks:
645 for r in repo.forks:
644 self.delete(r, forks='delete')
646 self.delete(r, forks='delete')
645 elif [f for f in repo.forks]:
647 elif [f for f in repo.forks]:
646 raise AttachedForksError()
648 raise AttachedForksError()
647
649
648 old_repo_dict = repo.get_dict()
650 old_repo_dict = repo.get_dict()
649 events.trigger(events.RepoPreDeleteEvent(repo))
651 events.trigger(events.RepoPreDeleteEvent(repo))
650 try:
652 try:
651 self.sa.delete(repo)
653 self.sa.delete(repo)
652 if fs_remove:
654 if fs_remove:
653 self._delete_filesystem_repo(repo)
655 self._delete_filesystem_repo(repo)
654 else:
656 else:
655 log.debug('skipping removal from filesystem')
657 log.debug('skipping removal from filesystem')
656 old_repo_dict.update({
658 old_repo_dict.update({
657 'deleted_by': cur_user,
659 'deleted_by': cur_user,
658 'deleted_on': time.time(),
660 'deleted_on': time.time(),
659 })
661 })
660 log_delete_repository(**old_repo_dict)
662 log_delete_repository(**old_repo_dict)
661 events.trigger(events.RepoDeleteEvent(repo))
663 events.trigger(events.RepoDeleteEvent(repo))
662 except Exception:
664 except Exception:
663 log.error(traceback.format_exc())
665 log.error(traceback.format_exc())
664 raise
666 raise
665
667
666 def grant_user_permission(self, repo, user, perm):
668 def grant_user_permission(self, repo, user, perm):
667 """
669 """
668 Grant permission for user on given repository, or update existing one
670 Grant permission for user on given repository, or update existing one
669 if found
671 if found
670
672
671 :param repo: Instance of Repository, repository_id, or repository name
673 :param repo: Instance of Repository, repository_id, or repository name
672 :param user: Instance of User, user_id or username
674 :param user: Instance of User, user_id or username
673 :param perm: Instance of Permission, or permission_name
675 :param perm: Instance of Permission, or permission_name
674 """
676 """
675 user = self._get_user(user)
677 user = self._get_user(user)
676 repo = self._get_repo(repo)
678 repo = self._get_repo(repo)
677 permission = self._get_perm(perm)
679 permission = self._get_perm(perm)
678
680
679 # check if we have that permission already
681 # check if we have that permission already
680 obj = self.sa.query(UserRepoToPerm) \
682 obj = self.sa.query(UserRepoToPerm) \
681 .filter(UserRepoToPerm.user == user) \
683 .filter(UserRepoToPerm.user == user) \
682 .filter(UserRepoToPerm.repository == repo) \
684 .filter(UserRepoToPerm.repository == repo) \
683 .scalar()
685 .scalar()
684 if obj is None:
686 if obj is None:
685 # create new !
687 # create new !
686 obj = UserRepoToPerm()
688 obj = UserRepoToPerm()
687 obj.repository = repo
689 obj.repository = repo
688 obj.user = user
690 obj.user = user
689 obj.permission = permission
691 obj.permission = permission
690 self.sa.add(obj)
692 self.sa.add(obj)
691 log.debug('Granted perm %s to %s on %s', perm, user, repo)
693 log.debug('Granted perm %s to %s on %s', perm, user, repo)
692 action_logger_generic(
694 action_logger_generic(
693 'granted permission: {} to user: {} on repo: {}'.format(
695 'granted permission: {} to user: {} on repo: {}'.format(
694 perm, user, repo), namespace='security.repo')
696 perm, user, repo), namespace='security.repo')
695 return obj
697 return obj
696
698
697 def revoke_user_permission(self, repo, user):
699 def revoke_user_permission(self, repo, user):
698 """
700 """
699 Revoke permission for user on given repository
701 Revoke permission for user on given repository
700
702
701 :param repo: Instance of Repository, repository_id, or repository name
703 :param repo: Instance of Repository, repository_id, or repository name
702 :param user: Instance of User, user_id or username
704 :param user: Instance of User, user_id or username
703 """
705 """
704
706
705 user = self._get_user(user)
707 user = self._get_user(user)
706 repo = self._get_repo(repo)
708 repo = self._get_repo(repo)
707
709
708 obj = self.sa.query(UserRepoToPerm) \
710 obj = self.sa.query(UserRepoToPerm) \
709 .filter(UserRepoToPerm.repository == repo) \
711 .filter(UserRepoToPerm.repository == repo) \
710 .filter(UserRepoToPerm.user == user) \
712 .filter(UserRepoToPerm.user == user) \
711 .scalar()
713 .scalar()
712 if obj:
714 if obj:
713 self.sa.delete(obj)
715 self.sa.delete(obj)
714 log.debug('Revoked perm on %s on %s', repo, user)
716 log.debug('Revoked perm on %s on %s', repo, user)
715 action_logger_generic(
717 action_logger_generic(
716 'revoked permission from user: {} on repo: {}'.format(
718 'revoked permission from user: {} on repo: {}'.format(
717 user, repo), namespace='security.repo')
719 user, repo), namespace='security.repo')
718
720
719 def grant_user_group_permission(self, repo, group_name, perm):
721 def grant_user_group_permission(self, repo, group_name, perm):
720 """
722 """
721 Grant permission for user group on given repository, or update
723 Grant permission for user group on given repository, or update
722 existing one if found
724 existing one if found
723
725
724 :param repo: Instance of Repository, repository_id, or repository name
726 :param repo: Instance of Repository, repository_id, or repository name
725 :param group_name: Instance of UserGroup, users_group_id,
727 :param group_name: Instance of UserGroup, users_group_id,
726 or user group name
728 or user group name
727 :param perm: Instance of Permission, or permission_name
729 :param perm: Instance of Permission, or permission_name
728 """
730 """
729 repo = self._get_repo(repo)
731 repo = self._get_repo(repo)
730 group_name = self._get_user_group(group_name)
732 group_name = self._get_user_group(group_name)
731 permission = self._get_perm(perm)
733 permission = self._get_perm(perm)
732
734
733 # check if we have that permission already
735 # check if we have that permission already
734 obj = self.sa.query(UserGroupRepoToPerm) \
736 obj = self.sa.query(UserGroupRepoToPerm) \
735 .filter(UserGroupRepoToPerm.users_group == group_name) \
737 .filter(UserGroupRepoToPerm.users_group == group_name) \
736 .filter(UserGroupRepoToPerm.repository == repo) \
738 .filter(UserGroupRepoToPerm.repository == repo) \
737 .scalar()
739 .scalar()
738
740
739 if obj is None:
741 if obj is None:
740 # create new
742 # create new
741 obj = UserGroupRepoToPerm()
743 obj = UserGroupRepoToPerm()
742
744
743 obj.repository = repo
745 obj.repository = repo
744 obj.users_group = group_name
746 obj.users_group = group_name
745 obj.permission = permission
747 obj.permission = permission
746 self.sa.add(obj)
748 self.sa.add(obj)
747 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
749 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
748 action_logger_generic(
750 action_logger_generic(
749 'granted permission: {} to usergroup: {} on repo: {}'.format(
751 'granted permission: {} to usergroup: {} on repo: {}'.format(
750 perm, group_name, repo), namespace='security.repo')
752 perm, group_name, repo), namespace='security.repo')
751
753
752 return obj
754 return obj
753
755
754 def revoke_user_group_permission(self, repo, group_name):
756 def revoke_user_group_permission(self, repo, group_name):
755 """
757 """
756 Revoke permission for user group on given repository
758 Revoke permission for user group on given repository
757
759
758 :param repo: Instance of Repository, repository_id, or repository name
760 :param repo: Instance of Repository, repository_id, or repository name
759 :param group_name: Instance of UserGroup, users_group_id,
761 :param group_name: Instance of UserGroup, users_group_id,
760 or user group name
762 or user group name
761 """
763 """
762 repo = self._get_repo(repo)
764 repo = self._get_repo(repo)
763 group_name = self._get_user_group(group_name)
765 group_name = self._get_user_group(group_name)
764
766
765 obj = self.sa.query(UserGroupRepoToPerm) \
767 obj = self.sa.query(UserGroupRepoToPerm) \
766 .filter(UserGroupRepoToPerm.repository == repo) \
768 .filter(UserGroupRepoToPerm.repository == repo) \
767 .filter(UserGroupRepoToPerm.users_group == group_name) \
769 .filter(UserGroupRepoToPerm.users_group == group_name) \
768 .scalar()
770 .scalar()
769 if obj:
771 if obj:
770 self.sa.delete(obj)
772 self.sa.delete(obj)
771 log.debug('Revoked perm to %s on %s', repo, group_name)
773 log.debug('Revoked perm to %s on %s', repo, group_name)
772 action_logger_generic(
774 action_logger_generic(
773 'revoked permission from usergroup: {} on repo: {}'.format(
775 'revoked permission from usergroup: {} on repo: {}'.format(
774 group_name, repo), namespace='security.repo')
776 group_name, repo), namespace='security.repo')
775
777
776 def delete_stats(self, repo_name):
778 def delete_stats(self, repo_name):
777 """
779 """
778 removes stats for given repo
780 removes stats for given repo
779
781
780 :param repo_name:
782 :param repo_name:
781 """
783 """
782 repo = self._get_repo(repo_name)
784 repo = self._get_repo(repo_name)
783 try:
785 try:
784 obj = self.sa.query(Statistics) \
786 obj = self.sa.query(Statistics) \
785 .filter(Statistics.repository == repo).scalar()
787 .filter(Statistics.repository == repo).scalar()
786 if obj:
788 if obj:
787 self.sa.delete(obj)
789 self.sa.delete(obj)
788 except Exception:
790 except Exception:
789 log.error(traceback.format_exc())
791 log.error(traceback.format_exc())
790 raise
792 raise
791
793
792 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
794 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
793 field_type='str', field_desc=''):
795 field_type='str', field_desc=''):
794
796
795 repo = self._get_repo(repo_name)
797 repo = self._get_repo(repo_name)
796
798
797 new_field = RepositoryField()
799 new_field = RepositoryField()
798 new_field.repository = repo
800 new_field.repository = repo
799 new_field.field_key = field_key
801 new_field.field_key = field_key
800 new_field.field_type = field_type # python type
802 new_field.field_type = field_type # python type
801 new_field.field_value = field_value
803 new_field.field_value = field_value
802 new_field.field_desc = field_desc
804 new_field.field_desc = field_desc
803 new_field.field_label = field_label
805 new_field.field_label = field_label
804 self.sa.add(new_field)
806 self.sa.add(new_field)
805 return new_field
807 return new_field
806
808
807 def delete_repo_field(self, repo_name, field_key):
809 def delete_repo_field(self, repo_name, field_key):
808 repo = self._get_repo(repo_name)
810 repo = self._get_repo(repo_name)
809 field = RepositoryField.get_by_key_name(field_key, repo)
811 field = RepositoryField.get_by_key_name(field_key, repo)
810 if field:
812 if field:
811 self.sa.delete(field)
813 self.sa.delete(field)
812
814
813 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
815 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
814 clone_uri=None, repo_store_location=None,
816 clone_uri=None, repo_store_location=None,
815 use_global_config=False):
817 use_global_config=False):
816 """
818 """
817 makes repository on filesystem. It's group aware means it'll create
819 makes repository on filesystem. It's group aware means it'll create
818 a repository within a group, and alter the paths accordingly of
820 a repository within a group, and alter the paths accordingly of
819 group location
821 group location
820
822
821 :param repo_name:
823 :param repo_name:
822 :param alias:
824 :param alias:
823 :param parent:
825 :param parent:
824 :param clone_uri:
826 :param clone_uri:
825 :param repo_store_location:
827 :param repo_store_location:
826 """
828 """
827 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
829 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
828 from rhodecode.model.scm import ScmModel
830 from rhodecode.model.scm import ScmModel
829
831
830 if Repository.NAME_SEP in repo_name:
832 if Repository.NAME_SEP in repo_name:
831 raise ValueError(
833 raise ValueError(
832 'repo_name must not contain groups got `%s`' % repo_name)
834 'repo_name must not contain groups got `%s`' % repo_name)
833
835
834 if isinstance(repo_group, RepoGroup):
836 if isinstance(repo_group, RepoGroup):
835 new_parent_path = os.sep.join(repo_group.full_path_splitted)
837 new_parent_path = os.sep.join(repo_group.full_path_splitted)
836 else:
838 else:
837 new_parent_path = repo_group or ''
839 new_parent_path = repo_group or ''
838
840
839 if repo_store_location:
841 if repo_store_location:
840 _paths = [repo_store_location]
842 _paths = [repo_store_location]
841 else:
843 else:
842 _paths = [self.repos_path, new_parent_path, repo_name]
844 _paths = [self.repos_path, new_parent_path, repo_name]
843 # we need to make it str for mercurial
845 # we need to make it str for mercurial
844 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
846 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
845
847
846 # check if this path is not a repository
848 # check if this path is not a repository
847 if is_valid_repo(repo_path, self.repos_path):
849 if is_valid_repo(repo_path, self.repos_path):
848 raise Exception('This path %s is a valid repository' % repo_path)
850 raise Exception('This path %s is a valid repository' % repo_path)
849
851
850 # check if this path is a group
852 # check if this path is a group
851 if is_valid_repo_group(repo_path, self.repos_path):
853 if is_valid_repo_group(repo_path, self.repos_path):
852 raise Exception('This path %s is a valid group' % repo_path)
854 raise Exception('This path %s is a valid group' % repo_path)
853
855
854 log.info('creating repo %s in %s from url: `%s`',
856 log.info('creating repo %s in %s from url: `%s`',
855 repo_name, safe_unicode(repo_path),
857 repo_name, safe_unicode(repo_path),
856 obfuscate_url_pw(clone_uri))
858 obfuscate_url_pw(clone_uri))
857
859
858 backend = get_backend(repo_type)
860 backend = get_backend(repo_type)
859
861
860 config_repo = None if use_global_config else repo_name
862 config_repo = None if use_global_config else repo_name
861 if config_repo and new_parent_path:
863 if config_repo and new_parent_path:
862 config_repo = Repository.NAME_SEP.join(
864 config_repo = Repository.NAME_SEP.join(
863 (new_parent_path, config_repo))
865 (new_parent_path, config_repo))
864 config = make_db_config(clear_session=False, repo=config_repo)
866 config = make_db_config(clear_session=False, repo=config_repo)
865 config.set('extensions', 'largefiles', '')
867 config.set('extensions', 'largefiles', '')
866
868
867 # patch and reset hooks section of UI config to not run any
869 # patch and reset hooks section of UI config to not run any
868 # hooks on creating remote repo
870 # hooks on creating remote repo
869 config.clear_section('hooks')
871 config.clear_section('hooks')
870
872
871 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
873 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
872 if repo_type == 'git':
874 if repo_type == 'git':
873 repo = backend(
875 repo = backend(
874 repo_path, config=config, create=True, src_url=clone_uri,
876 repo_path, config=config, create=True, src_url=clone_uri,
875 bare=True)
877 bare=True)
876 else:
878 else:
877 repo = backend(
879 repo = backend(
878 repo_path, config=config, create=True, src_url=clone_uri)
880 repo_path, config=config, create=True, src_url=clone_uri)
879
881
880 ScmModel().install_hooks(repo, repo_type=repo_type)
882 ScmModel().install_hooks(repo, repo_type=repo_type)
881
883
882 log.debug('Created repo %s with %s backend',
884 log.debug('Created repo %s with %s backend',
883 safe_unicode(repo_name), safe_unicode(repo_type))
885 safe_unicode(repo_name), safe_unicode(repo_type))
884 return repo
886 return repo
885
887
886 def _rename_filesystem_repo(self, old, new):
888 def _rename_filesystem_repo(self, old, new):
887 """
889 """
888 renames repository on filesystem
890 renames repository on filesystem
889
891
890 :param old: old name
892 :param old: old name
891 :param new: new name
893 :param new: new name
892 """
894 """
893 log.info('renaming repo from %s to %s', old, new)
895 log.info('renaming repo from %s to %s', old, new)
894
896
895 old_path = os.path.join(self.repos_path, old)
897 old_path = os.path.join(self.repos_path, old)
896 new_path = os.path.join(self.repos_path, new)
898 new_path = os.path.join(self.repos_path, new)
897 if os.path.isdir(new_path):
899 if os.path.isdir(new_path):
898 raise Exception(
900 raise Exception(
899 'Was trying to rename to already existing dir %s' % new_path
901 'Was trying to rename to already existing dir %s' % new_path
900 )
902 )
901 shutil.move(old_path, new_path)
903 shutil.move(old_path, new_path)
902
904
903 def _delete_filesystem_repo(self, repo):
905 def _delete_filesystem_repo(self, repo):
904 """
906 """
905 removes repo from filesystem, the removal is acctually made by
907 removes repo from filesystem, the removal is acctually made by
906 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
908 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
907 repository is no longer valid for rhodecode, can be undeleted later on
909 repository is no longer valid for rhodecode, can be undeleted later on
908 by reverting the renames on this repository
910 by reverting the renames on this repository
909
911
910 :param repo: repo object
912 :param repo: repo object
911 """
913 """
912 rm_path = os.path.join(self.repos_path, repo.repo_name)
914 rm_path = os.path.join(self.repos_path, repo.repo_name)
913 repo_group = repo.group
915 repo_group = repo.group
914 log.info("Removing repository %s", rm_path)
916 log.info("Removing repository %s", rm_path)
915 # disable hg/git internal that it doesn't get detected as repo
917 # disable hg/git internal that it doesn't get detected as repo
916 alias = repo.repo_type
918 alias = repo.repo_type
917
919
918 config = make_db_config(clear_session=False)
920 config = make_db_config(clear_session=False)
919 config.set('extensions', 'largefiles', '')
921 config.set('extensions', 'largefiles', '')
920 bare = getattr(repo.scm_instance(config=config), 'bare', False)
922 bare = getattr(repo.scm_instance(config=config), 'bare', False)
921
923
922 # skip this for bare git repos
924 # skip this for bare git repos
923 if not bare:
925 if not bare:
924 # disable VCS repo
926 # disable VCS repo
925 vcs_path = os.path.join(rm_path, '.%s' % alias)
927 vcs_path = os.path.join(rm_path, '.%s' % alias)
926 if os.path.exists(vcs_path):
928 if os.path.exists(vcs_path):
927 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
929 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
928
930
929 _now = datetime.now()
931 _now = datetime.now()
930 _ms = str(_now.microsecond).rjust(6, '0')
932 _ms = str(_now.microsecond).rjust(6, '0')
931 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
933 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
932 repo.just_name)
934 repo.just_name)
933 if repo_group:
935 if repo_group:
934 # if repository is in group, prefix the removal path with the group
936 # if repository is in group, prefix the removal path with the group
935 args = repo_group.full_path_splitted + [_d]
937 args = repo_group.full_path_splitted + [_d]
936 _d = os.path.join(*args)
938 _d = os.path.join(*args)
937
939
938 if os.path.isdir(rm_path):
940 if os.path.isdir(rm_path):
939 shutil.move(rm_path, os.path.join(self.repos_path, _d))
941 shutil.move(rm_path, os.path.join(self.repos_path, _d))
940
942
941
943
942 class ReadmeFinder:
944 class ReadmeFinder:
943 """
945 """
944 Utility which knows how to find a readme for a specific commit.
946 Utility which knows how to find a readme for a specific commit.
945
947
946 The main idea is that this is a configurable algorithm. When creating an
948 The main idea is that this is a configurable algorithm. When creating an
947 instance you can define parameters, currently only the `default_renderer`.
949 instance you can define parameters, currently only the `default_renderer`.
948 Based on this configuration the method :meth:`search` behaves slightly
950 Based on this configuration the method :meth:`search` behaves slightly
949 different.
951 different.
950 """
952 """
951
953
952 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
954 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
953 path_re = re.compile(r'^docs?', re.IGNORECASE)
955 path_re = re.compile(r'^docs?', re.IGNORECASE)
954
956
955 default_priorities = {
957 default_priorities = {
956 None: 0,
958 None: 0,
957 '.text': 2,
959 '.text': 2,
958 '.txt': 3,
960 '.txt': 3,
959 '.rst': 1,
961 '.rst': 1,
960 '.rest': 2,
962 '.rest': 2,
961 '.md': 1,
963 '.md': 1,
962 '.mkdn': 2,
964 '.mkdn': 2,
963 '.mdown': 3,
965 '.mdown': 3,
964 '.markdown': 4,
966 '.markdown': 4,
965 }
967 }
966
968
967 path_priority = {
969 path_priority = {
968 'doc': 0,
970 'doc': 0,
969 'docs': 1,
971 'docs': 1,
970 }
972 }
971
973
972 FALLBACK_PRIORITY = 99
974 FALLBACK_PRIORITY = 99
973
975
974 RENDERER_TO_EXTENSION = {
976 RENDERER_TO_EXTENSION = {
975 'rst': ['.rst', '.rest'],
977 'rst': ['.rst', '.rest'],
976 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
978 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
977 }
979 }
978
980
979 def __init__(self, default_renderer=None):
981 def __init__(self, default_renderer=None):
980 self._default_renderer = default_renderer
982 self._default_renderer = default_renderer
981 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
983 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
982 default_renderer, [])
984 default_renderer, [])
983
985
984 def search(self, commit, path='/'):
986 def search(self, commit, path='/'):
985 """
987 """
986 Find a readme in the given `commit`.
988 Find a readme in the given `commit`.
987 """
989 """
988 nodes = commit.get_nodes(path)
990 nodes = commit.get_nodes(path)
989 matches = self._match_readmes(nodes)
991 matches = self._match_readmes(nodes)
990 matches = self._sort_according_to_priority(matches)
992 matches = self._sort_according_to_priority(matches)
991 if matches:
993 if matches:
992 return matches[0].node
994 return matches[0].node
993
995
994 paths = self._match_paths(nodes)
996 paths = self._match_paths(nodes)
995 paths = self._sort_paths_according_to_priority(paths)
997 paths = self._sort_paths_according_to_priority(paths)
996 for path in paths:
998 for path in paths:
997 match = self.search(commit, path=path)
999 match = self.search(commit, path=path)
998 if match:
1000 if match:
999 return match
1001 return match
1000
1002
1001 return None
1003 return None
1002
1004
1003 def _match_readmes(self, nodes):
1005 def _match_readmes(self, nodes):
1004 for node in nodes:
1006 for node in nodes:
1005 if not node.is_file():
1007 if not node.is_file():
1006 continue
1008 continue
1007 path = node.path.rsplit('/', 1)[-1]
1009 path = node.path.rsplit('/', 1)[-1]
1008 match = self.readme_re.match(path)
1010 match = self.readme_re.match(path)
1009 if match:
1011 if match:
1010 extension = match.group(1)
1012 extension = match.group(1)
1011 yield ReadmeMatch(node, match, self._priority(extension))
1013 yield ReadmeMatch(node, match, self._priority(extension))
1012
1014
1013 def _match_paths(self, nodes):
1015 def _match_paths(self, nodes):
1014 for node in nodes:
1016 for node in nodes:
1015 if not node.is_dir():
1017 if not node.is_dir():
1016 continue
1018 continue
1017 match = self.path_re.match(node.path)
1019 match = self.path_re.match(node.path)
1018 if match:
1020 if match:
1019 yield node.path
1021 yield node.path
1020
1022
1021 def _priority(self, extension):
1023 def _priority(self, extension):
1022 renderer_priority = (
1024 renderer_priority = (
1023 0 if extension in self._renderer_extensions else 1)
1025 0 if extension in self._renderer_extensions else 1)
1024 extension_priority = self.default_priorities.get(
1026 extension_priority = self.default_priorities.get(
1025 extension, self.FALLBACK_PRIORITY)
1027 extension, self.FALLBACK_PRIORITY)
1026 return (renderer_priority, extension_priority)
1028 return (renderer_priority, extension_priority)
1027
1029
1028 def _sort_according_to_priority(self, matches):
1030 def _sort_according_to_priority(self, matches):
1029
1031
1030 def priority_and_path(match):
1032 def priority_and_path(match):
1031 return (match.priority, match.path)
1033 return (match.priority, match.path)
1032
1034
1033 return sorted(matches, key=priority_and_path)
1035 return sorted(matches, key=priority_and_path)
1034
1036
1035 def _sort_paths_according_to_priority(self, paths):
1037 def _sort_paths_according_to_priority(self, paths):
1036
1038
1037 def priority_and_path(path):
1039 def priority_and_path(path):
1038 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1040 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1039
1041
1040 return sorted(paths, key=priority_and_path)
1042 return sorted(paths, key=priority_and_path)
1041
1043
1042
1044
1043 class ReadmeMatch:
1045 class ReadmeMatch:
1044
1046
1045 def __init__(self, node, match, priority):
1047 def __init__(self, node, match, priority):
1046 self.node = node
1048 self.node = node
1047 self._match = match
1049 self._match = match
1048 self.priority = priority
1050 self.priority = priority
1049
1051
1050 @property
1052 @property
1051 def path(self):
1053 def path(self):
1052 return self.node.path
1054 return self.node.path
1053
1055
1054 def __repr__(self):
1056 def __repr__(self):
1055 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
1057 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,701 +1,705 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2016 RhodeCode GmbH
3 # Copyright (C) 2011-2016 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 """
22 """
23 repo group model for RhodeCode
23 repo group model for RhodeCode
24 """
24 """
25
25
26 import os
26 import os
27 import datetime
27 import datetime
28 import itertools
28 import itertools
29 import logging
29 import logging
30 import shutil
30 import shutil
31 import traceback
31 import traceback
32 import string
32 import string
33
33
34 from zope.cachedescriptors.property import Lazy as LazyProperty
34 from zope.cachedescriptors.property import Lazy as LazyProperty
35
35
36 from rhodecode import events
36 from rhodecode import events
37 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import (
38 from rhodecode.model.db import (
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 UserGroup, Repository)
40 UserGroup, Repository)
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
42 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.lib.caching_query import FromCache
43 from rhodecode.lib.utils2 import action_logger_generic
43 from rhodecode.lib.utils2 import action_logger_generic
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class RepoGroupModel(BaseModel):
48 class RepoGroupModel(BaseModel):
49
49
50 cls = RepoGroup
50 cls = RepoGroup
51 PERSONAL_GROUP_DESC = '[personal] repo group: owner `%(username)s`'
51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 PERSONAL_GROUP_PATTERN = '${username}' # default
52 PERSONAL_GROUP_PATTERN = '${username}' # default
53
53
54 def _get_user_group(self, users_group):
54 def _get_user_group(self, users_group):
55 return self._get_instance(UserGroup, users_group,
55 return self._get_instance(UserGroup, users_group,
56 callback=UserGroup.get_by_group_name)
56 callback=UserGroup.get_by_group_name)
57
57
58 def _get_repo_group(self, repo_group):
58 def _get_repo_group(self, repo_group):
59 return self._get_instance(RepoGroup, repo_group,
59 return self._get_instance(RepoGroup, repo_group,
60 callback=RepoGroup.get_by_group_name)
60 callback=RepoGroup.get_by_group_name)
61
61
62 @LazyProperty
62 @LazyProperty
63 def repos_path(self):
63 def repos_path(self):
64 """
64 """
65 Gets the repositories root path from database
65 Gets the repositories root path from database
66 """
66 """
67
67
68 settings_model = VcsSettingsModel(sa=self.sa)
68 settings_model = VcsSettingsModel(sa=self.sa)
69 return settings_model.get_repos_location()
69 return settings_model.get_repos_location()
70
70
71 def get_by_group_name(self, repo_group_name, cache=None):
71 def get_by_group_name(self, repo_group_name, cache=None):
72 repo = self.sa.query(RepoGroup) \
72 repo = self.sa.query(RepoGroup) \
73 .filter(RepoGroup.group_name == repo_group_name)
73 .filter(RepoGroup.group_name == repo_group_name)
74
74
75 if cache:
75 if cache:
76 repo = repo.options(FromCache(
76 repo = repo.options(FromCache(
77 "sql_cache_short", "get_repo_group_%s" % repo_group_name))
77 "sql_cache_short", "get_repo_group_%s" % repo_group_name))
78 return repo.scalar()
78 return repo.scalar()
79
79
80 def get_default_create_personal_repo_group(self):
80 def get_default_create_personal_repo_group(self):
81 value = SettingsModel().get_setting_by_name(
81 value = SettingsModel().get_setting_by_name(
82 'create_personal_repo_group')
82 'create_personal_repo_group')
83 return value.app_settings_value if value else None or False
83 return value.app_settings_value if value else None or False
84
84
85 def get_personal_group_name_pattern(self):
85 def get_personal_group_name_pattern(self):
86 value = SettingsModel().get_setting_by_name(
86 value = SettingsModel().get_setting_by_name(
87 'personal_repo_group_pattern')
87 'personal_repo_group_pattern')
88 val = value.app_settings_value if value else None
88 val = value.app_settings_value if value else None
89 group_template = val or self.PERSONAL_GROUP_PATTERN
89 group_template = val or self.PERSONAL_GROUP_PATTERN
90
90
91 group_template = group_template.lstrip('/')
91 group_template = group_template.lstrip('/')
92 return group_template
92 return group_template
93
93
94 def get_personal_group_name(self, user):
94 def get_personal_group_name(self, user):
95 template = self.get_personal_group_name_pattern()
95 template = self.get_personal_group_name_pattern()
96 return string.Template(template).safe_substitute(
96 return string.Template(template).safe_substitute(
97 username=user.username,
97 username=user.username,
98 user_id=user.user_id,
98 user_id=user.user_id,
99 )
99 )
100
100
101 def create_personal_repo_group(self, user, commit_early=True):
101 def create_personal_repo_group(self, user, commit_early=True):
102 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
102 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
103 personal_repo_group_name = self.get_personal_group_name(user)
103 personal_repo_group_name = self.get_personal_group_name(user)
104
104
105 # create a new one
105 # create a new one
106 RepoGroupModel().create(
106 RepoGroupModel().create(
107 group_name=personal_repo_group_name,
107 group_name=personal_repo_group_name,
108 group_description=desc,
108 group_description=desc,
109 owner=user.username,
109 owner=user.username,
110 personal=True,
110 personal=True,
111 commit_early=commit_early)
111 commit_early=commit_early)
112
112
113 def _create_default_perms(self, new_group):
113 def _create_default_perms(self, new_group):
114 # create default permission
114 # create default permission
115 default_perm = 'group.read'
115 default_perm = 'group.read'
116 def_user = User.get_default_user()
116 def_user = User.get_default_user()
117 for p in def_user.user_perms:
117 for p in def_user.user_perms:
118 if p.permission.permission_name.startswith('group.'):
118 if p.permission.permission_name.startswith('group.'):
119 default_perm = p.permission.permission_name
119 default_perm = p.permission.permission_name
120 break
120 break
121
121
122 repo_group_to_perm = UserRepoGroupToPerm()
122 repo_group_to_perm = UserRepoGroupToPerm()
123 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
123 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
124
124
125 repo_group_to_perm.group = new_group
125 repo_group_to_perm.group = new_group
126 repo_group_to_perm.user_id = def_user.user_id
126 repo_group_to_perm.user_id = def_user.user_id
127 return repo_group_to_perm
127 return repo_group_to_perm
128
128
129 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False):
129 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False):
130 """
130 """
131 Get's the group name and a parent group name from given group name.
131 Get's the group name and a parent group name from given group name.
132 If repo_in_path is set to truth, we asume the full path also includes
132 If repo_in_path is set to truth, we asume the full path also includes
133 repo name, in such case we clean the last element.
133 repo name, in such case we clean the last element.
134
134
135 :param group_name_full:
135 :param group_name_full:
136 """
136 """
137 split_paths = 1
137 split_paths = 1
138 if repo_in_path:
138 if repo_in_path:
139 split_paths = 2
139 split_paths = 2
140 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
140 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
141
141
142 if repo_in_path and len(_parts) > 1:
142 if repo_in_path and len(_parts) > 1:
143 # such case last element is the repo_name
143 # such case last element is the repo_name
144 _parts.pop(-1)
144 _parts.pop(-1)
145 group_name_cleaned = _parts[-1] # just the group name
145 group_name_cleaned = _parts[-1] # just the group name
146 parent_repo_group_name = None
146 parent_repo_group_name = None
147
147
148 if len(_parts) > 1:
148 if len(_parts) > 1:
149 parent_repo_group_name = _parts[0]
149 parent_repo_group_name = _parts[0]
150
150
151 if parent_repo_group_name:
151 if parent_repo_group_name:
152 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
152 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
153
153
154 return group_name_cleaned, parent_repo_group_name
154 return group_name_cleaned, parent_repo_group_name
155
155
156 def check_exist_filesystem(self, group_name, exc_on_failure=True):
156 def check_exist_filesystem(self, group_name, exc_on_failure=True):
157 create_path = os.path.join(self.repos_path, group_name)
157 create_path = os.path.join(self.repos_path, group_name)
158 log.debug('creating new group in %s', create_path)
158 log.debug('creating new group in %s', create_path)
159
159
160 if os.path.isdir(create_path):
160 if os.path.isdir(create_path):
161 if exc_on_failure:
161 if exc_on_failure:
162 raise Exception('That directory already exists !')
162 raise Exception('That directory already exists !')
163 return False
163 return False
164 return True
164 return True
165
165
166 def _create_group(self, group_name):
166 def _create_group(self, group_name):
167 """
167 """
168 makes repository group on filesystem
168 makes repository group on filesystem
169
169
170 :param repo_name:
170 :param repo_name:
171 :param parent_id:
171 :param parent_id:
172 """
172 """
173
173
174 self.check_exist_filesystem(group_name)
174 self.check_exist_filesystem(group_name)
175 create_path = os.path.join(self.repos_path, group_name)
175 create_path = os.path.join(self.repos_path, group_name)
176 log.debug('creating new group in %s', create_path)
176 log.debug('creating new group in %s', create_path)
177 os.makedirs(create_path, mode=0755)
177 os.makedirs(create_path, mode=0755)
178 log.debug('created group in %s', create_path)
178 log.debug('created group in %s', create_path)
179
179
180 def _rename_group(self, old, new):
180 def _rename_group(self, old, new):
181 """
181 """
182 Renames a group on filesystem
182 Renames a group on filesystem
183
183
184 :param group_name:
184 :param group_name:
185 """
185 """
186
186
187 if old == new:
187 if old == new:
188 log.debug('skipping group rename')
188 log.debug('skipping group rename')
189 return
189 return
190
190
191 log.debug('renaming repository group from %s to %s', old, new)
191 log.debug('renaming repository group from %s to %s', old, new)
192
192
193 old_path = os.path.join(self.repos_path, old)
193 old_path = os.path.join(self.repos_path, old)
194 new_path = os.path.join(self.repos_path, new)
194 new_path = os.path.join(self.repos_path, new)
195
195
196 log.debug('renaming repos paths from %s to %s', old_path, new_path)
196 log.debug('renaming repos paths from %s to %s', old_path, new_path)
197
197
198 if os.path.isdir(new_path):
198 if os.path.isdir(new_path):
199 raise Exception('Was trying to rename to already '
199 raise Exception('Was trying to rename to already '
200 'existing dir %s' % new_path)
200 'existing dir %s' % new_path)
201 shutil.move(old_path, new_path)
201 shutil.move(old_path, new_path)
202
202
203 def _delete_filesystem_group(self, group, force_delete=False):
203 def _delete_filesystem_group(self, group, force_delete=False):
204 """
204 """
205 Deletes a group from a filesystem
205 Deletes a group from a filesystem
206
206
207 :param group: instance of group from database
207 :param group: instance of group from database
208 :param force_delete: use shutil rmtree to remove all objects
208 :param force_delete: use shutil rmtree to remove all objects
209 """
209 """
210 paths = group.full_path.split(RepoGroup.url_sep())
210 paths = group.full_path.split(RepoGroup.url_sep())
211 paths = os.sep.join(paths)
211 paths = os.sep.join(paths)
212
212
213 rm_path = os.path.join(self.repos_path, paths)
213 rm_path = os.path.join(self.repos_path, paths)
214 log.info("Removing group %s", rm_path)
214 log.info("Removing group %s", rm_path)
215 # delete only if that path really exists
215 # delete only if that path really exists
216 if os.path.isdir(rm_path):
216 if os.path.isdir(rm_path):
217 if force_delete:
217 if force_delete:
218 shutil.rmtree(rm_path)
218 shutil.rmtree(rm_path)
219 else:
219 else:
220 # archive that group`
220 # archive that group`
221 _now = datetime.datetime.now()
221 _now = datetime.datetime.now()
222 _ms = str(_now.microsecond).rjust(6, '0')
222 _ms = str(_now.microsecond).rjust(6, '0')
223 _d = 'rm__%s_GROUP_%s' % (
223 _d = 'rm__%s_GROUP_%s' % (
224 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
224 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
225 shutil.move(rm_path, os.path.join(self.repos_path, _d))
225 shutil.move(rm_path, os.path.join(self.repos_path, _d))
226
226
227 def create(self, group_name, group_description, owner, just_db=False,
227 def create(self, group_name, group_description, owner, just_db=False,
228 copy_permissions=False, personal=None, commit_early=True):
228 copy_permissions=False, personal=None, commit_early=True):
229
229
230 (group_name_cleaned,
230 (group_name_cleaned,
231 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
231 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
232
232
233 parent_group = None
233 parent_group = None
234 if parent_group_name:
234 if parent_group_name:
235 parent_group = self._get_repo_group(parent_group_name)
235 parent_group = self._get_repo_group(parent_group_name)
236 if not parent_group:
236 if not parent_group:
237 # we tried to create a nested group, but the parent is not
237 # we tried to create a nested group, but the parent is not
238 # existing
238 # existing
239 raise ValueError(
239 raise ValueError(
240 'Parent group `%s` given in `%s` group name '
240 'Parent group `%s` given in `%s` group name '
241 'is not yet existing.' % (parent_group_name, group_name))
241 'is not yet existing.' % (parent_group_name, group_name))
242
242
243 # because we are doing a cleanup, we need to check if such directory
243 # because we are doing a cleanup, we need to check if such directory
244 # already exists. If we don't do that we can accidentally delete
244 # already exists. If we don't do that we can accidentally delete
245 # existing directory via cleanup that can cause data issues, since
245 # existing directory via cleanup that can cause data issues, since
246 # delete does a folder rename to special syntax later cleanup
246 # delete does a folder rename to special syntax later cleanup
247 # functions can delete this
247 # functions can delete this
248 cleanup_group = self.check_exist_filesystem(group_name,
248 cleanup_group = self.check_exist_filesystem(group_name,
249 exc_on_failure=False)
249 exc_on_failure=False)
250 try:
250 try:
251 user = self._get_user(owner)
251 user = self._get_user(owner)
252 new_repo_group = RepoGroup()
252 new_repo_group = RepoGroup()
253 new_repo_group.user = user
253 new_repo_group.user = user
254 new_repo_group.group_description = group_description or group_name
254 new_repo_group.group_description = group_description or group_name
255 new_repo_group.parent_group = parent_group
255 new_repo_group.parent_group = parent_group
256 new_repo_group.group_name = group_name
256 new_repo_group.group_name = group_name
257 new_repo_group.personal = personal
257 new_repo_group.personal = personal
258
258
259 self.sa.add(new_repo_group)
259 self.sa.add(new_repo_group)
260
260
261 # create an ADMIN permission for owner except if we're super admin,
261 # create an ADMIN permission for owner except if we're super admin,
262 # later owner should go into the owner field of groups
262 # later owner should go into the owner field of groups
263 if not user.is_admin:
263 if not user.is_admin:
264 self.grant_user_permission(repo_group=new_repo_group,
264 self.grant_user_permission(repo_group=new_repo_group,
265 user=owner, perm='group.admin')
265 user=owner, perm='group.admin')
266
266
267 if parent_group and copy_permissions:
267 if parent_group and copy_permissions:
268 # copy permissions from parent
268 # copy permissions from parent
269 user_perms = UserRepoGroupToPerm.query() \
269 user_perms = UserRepoGroupToPerm.query() \
270 .filter(UserRepoGroupToPerm.group == parent_group).all()
270 .filter(UserRepoGroupToPerm.group == parent_group).all()
271
271
272 group_perms = UserGroupRepoGroupToPerm.query() \
272 group_perms = UserGroupRepoGroupToPerm.query() \
273 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
273 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
274
274
275 for perm in user_perms:
275 for perm in user_perms:
276 # don't copy over the permission for user who is creating
276 # don't copy over the permission for user who is creating
277 # this group, if he is not super admin he get's admin
277 # this group, if he is not super admin he get's admin
278 # permission set above
278 # permission set above
279 if perm.user != user or user.is_admin:
279 if perm.user != user or user.is_admin:
280 UserRepoGroupToPerm.create(
280 UserRepoGroupToPerm.create(
281 perm.user, new_repo_group, perm.permission)
281 perm.user, new_repo_group, perm.permission)
282
282
283 for perm in group_perms:
283 for perm in group_perms:
284 UserGroupRepoGroupToPerm.create(
284 UserGroupRepoGroupToPerm.create(
285 perm.users_group, new_repo_group, perm.permission)
285 perm.users_group, new_repo_group, perm.permission)
286 else:
286 else:
287 perm_obj = self._create_default_perms(new_repo_group)
287 perm_obj = self._create_default_perms(new_repo_group)
288 self.sa.add(perm_obj)
288 self.sa.add(perm_obj)
289
289
290 # now commit the changes, earlier so we are sure everything is in
290 # now commit the changes, earlier so we are sure everything is in
291 # the database.
291 # the database.
292 if commit_early:
292 if commit_early:
293 self.sa.commit()
293 self.sa.commit()
294 if not just_db:
294 if not just_db:
295 self._create_group(new_repo_group.group_name)
295 self._create_group(new_repo_group.group_name)
296
296
297 # trigger the post hook
297 # trigger the post hook
298 from rhodecode.lib.hooks_base import log_create_repository_group
298 from rhodecode.lib.hooks_base import log_create_repository_group
299 repo_group = RepoGroup.get_by_group_name(group_name)
299 repo_group = RepoGroup.get_by_group_name(group_name)
300 log_create_repository_group(
300 log_create_repository_group(
301 created_by=user.username, **repo_group.get_dict())
301 created_by=user.username, **repo_group.get_dict())
302
302
303 # Trigger create event.
303 # Trigger create event.
304 events.trigger(events.RepoGroupCreateEvent(repo_group))
304 events.trigger(events.RepoGroupCreateEvent(repo_group))
305
305
306 return new_repo_group
306 return new_repo_group
307 except Exception:
307 except Exception:
308 self.sa.rollback()
308 self.sa.rollback()
309 log.exception('Exception occurred when creating repository group, '
309 log.exception('Exception occurred when creating repository group, '
310 'doing cleanup...')
310 'doing cleanup...')
311 # rollback things manually !
311 # rollback things manually !
312 repo_group = RepoGroup.get_by_group_name(group_name)
312 repo_group = RepoGroup.get_by_group_name(group_name)
313 if repo_group:
313 if repo_group:
314 RepoGroup.delete(repo_group.group_id)
314 RepoGroup.delete(repo_group.group_id)
315 self.sa.commit()
315 self.sa.commit()
316 if cleanup_group:
316 if cleanup_group:
317 RepoGroupModel()._delete_filesystem_group(repo_group)
317 RepoGroupModel()._delete_filesystem_group(repo_group)
318 raise
318 raise
319
319
320 def update_permissions(
320 def update_permissions(
321 self, repo_group, perm_additions=None, perm_updates=None,
321 self, repo_group, perm_additions=None, perm_updates=None,
322 perm_deletions=None, recursive=None, check_perms=True,
322 perm_deletions=None, recursive=None, check_perms=True,
323 cur_user=None):
323 cur_user=None):
324 from rhodecode.model.repo import RepoModel
324 from rhodecode.model.repo import RepoModel
325 from rhodecode.lib.auth import HasUserGroupPermissionAny
325 from rhodecode.lib.auth import HasUserGroupPermissionAny
326
326
327 if not perm_additions:
327 if not perm_additions:
328 perm_additions = []
328 perm_additions = []
329 if not perm_updates:
329 if not perm_updates:
330 perm_updates = []
330 perm_updates = []
331 if not perm_deletions:
331 if not perm_deletions:
332 perm_deletions = []
332 perm_deletions = []
333
333
334 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
334 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
335
335
336 def _set_perm_user(obj, user, perm):
336 def _set_perm_user(obj, user, perm):
337 if isinstance(obj, RepoGroup):
337 if isinstance(obj, RepoGroup):
338 self.grant_user_permission(
338 self.grant_user_permission(
339 repo_group=obj, user=user, perm=perm)
339 repo_group=obj, user=user, perm=perm)
340 elif isinstance(obj, Repository):
340 elif isinstance(obj, Repository):
341 # private repos will not allow to change the default
341 # private repos will not allow to change the default
342 # permissions using recursive mode
342 # permissions using recursive mode
343 if obj.private and user == User.DEFAULT_USER:
343 if obj.private and user == User.DEFAULT_USER:
344 return
344 return
345
345
346 # we set group permission but we have to switch to repo
346 # we set group permission but we have to switch to repo
347 # permission
347 # permission
348 perm = perm.replace('group.', 'repository.')
348 perm = perm.replace('group.', 'repository.')
349 RepoModel().grant_user_permission(
349 RepoModel().grant_user_permission(
350 repo=obj, user=user, perm=perm)
350 repo=obj, user=user, perm=perm)
351
351
352 def _set_perm_group(obj, users_group, perm):
352 def _set_perm_group(obj, users_group, perm):
353 if isinstance(obj, RepoGroup):
353 if isinstance(obj, RepoGroup):
354 self.grant_user_group_permission(
354 self.grant_user_group_permission(
355 repo_group=obj, group_name=users_group, perm=perm)
355 repo_group=obj, group_name=users_group, perm=perm)
356 elif isinstance(obj, Repository):
356 elif isinstance(obj, Repository):
357 # we set group permission but we have to switch to repo
357 # we set group permission but we have to switch to repo
358 # permission
358 # permission
359 perm = perm.replace('group.', 'repository.')
359 perm = perm.replace('group.', 'repository.')
360 RepoModel().grant_user_group_permission(
360 RepoModel().grant_user_group_permission(
361 repo=obj, group_name=users_group, perm=perm)
361 repo=obj, group_name=users_group, perm=perm)
362
362
363 def _revoke_perm_user(obj, user):
363 def _revoke_perm_user(obj, user):
364 if isinstance(obj, RepoGroup):
364 if isinstance(obj, RepoGroup):
365 self.revoke_user_permission(repo_group=obj, user=user)
365 self.revoke_user_permission(repo_group=obj, user=user)
366 elif isinstance(obj, Repository):
366 elif isinstance(obj, Repository):
367 RepoModel().revoke_user_permission(repo=obj, user=user)
367 RepoModel().revoke_user_permission(repo=obj, user=user)
368
368
369 def _revoke_perm_group(obj, user_group):
369 def _revoke_perm_group(obj, user_group):
370 if isinstance(obj, RepoGroup):
370 if isinstance(obj, RepoGroup):
371 self.revoke_user_group_permission(
371 self.revoke_user_group_permission(
372 repo_group=obj, group_name=user_group)
372 repo_group=obj, group_name=user_group)
373 elif isinstance(obj, Repository):
373 elif isinstance(obj, Repository):
374 RepoModel().revoke_user_group_permission(
374 RepoModel().revoke_user_group_permission(
375 repo=obj, group_name=user_group)
375 repo=obj, group_name=user_group)
376
376
377 # start updates
377 # start updates
378 updates = []
378 updates = []
379 log.debug('Now updating permissions for %s in recursive mode:%s',
379 log.debug('Now updating permissions for %s in recursive mode:%s',
380 repo_group, recursive)
380 repo_group, recursive)
381
381
382 # initialize check function, we'll call that multiple times
382 # initialize check function, we'll call that multiple times
383 has_group_perm = HasUserGroupPermissionAny(*req_perms)
383 has_group_perm = HasUserGroupPermissionAny(*req_perms)
384
384
385 for obj in repo_group.recursive_groups_and_repos():
385 for obj in repo_group.recursive_groups_and_repos():
386 # iterated obj is an instance of a repos group or repository in
386 # iterated obj is an instance of a repos group or repository in
387 # that group, recursive option can be: none, repos, groups, all
387 # that group, recursive option can be: none, repos, groups, all
388 if recursive == 'all':
388 if recursive == 'all':
389 obj = obj
389 obj = obj
390 elif recursive == 'repos':
390 elif recursive == 'repos':
391 # skip groups, other than this one
391 # skip groups, other than this one
392 if isinstance(obj, RepoGroup) and not obj == repo_group:
392 if isinstance(obj, RepoGroup) and not obj == repo_group:
393 continue
393 continue
394 elif recursive == 'groups':
394 elif recursive == 'groups':
395 # skip repos
395 # skip repos
396 if isinstance(obj, Repository):
396 if isinstance(obj, Repository):
397 continue
397 continue
398 else: # recursive == 'none':
398 else: # recursive == 'none':
399 # DEFAULT option - don't apply to iterated objects
399 # DEFAULT option - don't apply to iterated objects
400 # also we do a break at the end of this loop. if we are not
400 # also we do a break at the end of this loop. if we are not
401 # in recursive mode
401 # in recursive mode
402 obj = repo_group
402 obj = repo_group
403
403
404 # update permissions
404 # update permissions
405 for member_id, perm, member_type in perm_updates:
405 for member_id, perm, member_type in perm_updates:
406 member_id = int(member_id)
406 member_id = int(member_id)
407 if member_type == 'user':
407 if member_type == 'user':
408 # this updates also current one if found
408 # this updates also current one if found
409 _set_perm_user(obj, user=member_id, perm=perm)
409 _set_perm_user(obj, user=member_id, perm=perm)
410 else: # set for user group
410 else: # set for user group
411 member_name = UserGroup.get(member_id).users_group_name
411 member_name = UserGroup.get(member_id).users_group_name
412 if not check_perms or has_group_perm(member_name,
412 if not check_perms or has_group_perm(member_name,
413 user=cur_user):
413 user=cur_user):
414 _set_perm_group(obj, users_group=member_id, perm=perm)
414 _set_perm_group(obj, users_group=member_id, perm=perm)
415
415
416 # set new permissions
416 # set new permissions
417 for member_id, perm, member_type in perm_additions:
417 for member_id, perm, member_type in perm_additions:
418 member_id = int(member_id)
418 member_id = int(member_id)
419 if member_type == 'user':
419 if member_type == 'user':
420 _set_perm_user(obj, user=member_id, perm=perm)
420 _set_perm_user(obj, user=member_id, perm=perm)
421 else: # set for user group
421 else: # set for user group
422 # check if we have permissions to alter this usergroup
422 # check if we have permissions to alter this usergroup
423 member_name = UserGroup.get(member_id).users_group_name
423 member_name = UserGroup.get(member_id).users_group_name
424 if not check_perms or has_group_perm(member_name,
424 if not check_perms or has_group_perm(member_name,
425 user=cur_user):
425 user=cur_user):
426 _set_perm_group(obj, users_group=member_id, perm=perm)
426 _set_perm_group(obj, users_group=member_id, perm=perm)
427
427
428 # delete permissions
428 # delete permissions
429 for member_id, perm, member_type in perm_deletions:
429 for member_id, perm, member_type in perm_deletions:
430 member_id = int(member_id)
430 member_id = int(member_id)
431 if member_type == 'user':
431 if member_type == 'user':
432 _revoke_perm_user(obj, user=member_id)
432 _revoke_perm_user(obj, user=member_id)
433 else: # set for user group
433 else: # set for user group
434 # check if we have permissions to alter this usergroup
434 # check if we have permissions to alter this usergroup
435 member_name = UserGroup.get(member_id).users_group_name
435 member_name = UserGroup.get(member_id).users_group_name
436 if not check_perms or has_group_perm(member_name,
436 if not check_perms or has_group_perm(member_name,
437 user=cur_user):
437 user=cur_user):
438 _revoke_perm_group(obj, user_group=member_id)
438 _revoke_perm_group(obj, user_group=member_id)
439
439
440 updates.append(obj)
440 updates.append(obj)
441 # if it's not recursive call for all,repos,groups
441 # if it's not recursive call for all,repos,groups
442 # break the loop and don't proceed with other changes
442 # break the loop and don't proceed with other changes
443 if recursive not in ['all', 'repos', 'groups']:
443 if recursive not in ['all', 'repos', 'groups']:
444 break
444 break
445
445
446 return updates
446 return updates
447
447
448 def update(self, repo_group, form_data):
448 def update(self, repo_group, form_data):
449 try:
449 try:
450 repo_group = self._get_repo_group(repo_group)
450 repo_group = self._get_repo_group(repo_group)
451 old_path = repo_group.full_path
451 old_path = repo_group.full_path
452
452
453 # change properties
453 # change properties
454 if 'group_description' in form_data:
454 if 'group_description' in form_data:
455 repo_group.group_description = form_data['group_description']
455 repo_group.group_description = form_data['group_description']
456
456
457 if 'enable_locking' in form_data:
457 if 'enable_locking' in form_data:
458 repo_group.enable_locking = form_data['enable_locking']
458 repo_group.enable_locking = form_data['enable_locking']
459
459
460 if 'group_parent_id' in form_data:
460 if 'group_parent_id' in form_data:
461 parent_group = (
461 parent_group = (
462 self._get_repo_group(form_data['group_parent_id']))
462 self._get_repo_group(form_data['group_parent_id']))
463 repo_group.group_parent_id = (
463 repo_group.group_parent_id = (
464 parent_group.group_id if parent_group else None)
464 parent_group.group_id if parent_group else None)
465 repo_group.parent_group = parent_group
465 repo_group.parent_group = parent_group
466
466
467 # mikhail: to update the full_path, we have to explicitly
467 # mikhail: to update the full_path, we have to explicitly
468 # update group_name
468 # update group_name
469 group_name = form_data.get('group_name', repo_group.name)
469 group_name = form_data.get('group_name', repo_group.name)
470 repo_group.group_name = repo_group.get_new_name(group_name)
470 repo_group.group_name = repo_group.get_new_name(group_name)
471
471
472 new_path = repo_group.full_path
472 new_path = repo_group.full_path
473
473
474 if 'user' in form_data:
474 if 'user' in form_data:
475 repo_group.user = User.get_by_username(form_data['user'])
475 repo_group.user = User.get_by_username(form_data['user'])
476
476
477 self.sa.add(repo_group)
477 self.sa.add(repo_group)
478
478
479 # iterate over all members of this groups and do fixes
479 # iterate over all members of this groups and do fixes
480 # set locking if given
480 # set locking if given
481 # if obj is a repoGroup also fix the name of the group according
481 # if obj is a repoGroup also fix the name of the group according
482 # to the parent
482 # to the parent
483 # if obj is a Repo fix it's name
483 # if obj is a Repo fix it's name
484 # this can be potentially heavy operation
484 # this can be potentially heavy operation
485 for obj in repo_group.recursive_groups_and_repos():
485 for obj in repo_group.recursive_groups_and_repos():
486 # set the value from it's parent
486 # set the value from it's parent
487 obj.enable_locking = repo_group.enable_locking
487 obj.enable_locking = repo_group.enable_locking
488 if isinstance(obj, RepoGroup):
488 if isinstance(obj, RepoGroup):
489 new_name = obj.get_new_name(obj.name)
489 new_name = obj.get_new_name(obj.name)
490 log.debug('Fixing group %s to new name %s',
490 log.debug('Fixing group %s to new name %s',
491 obj.group_name, new_name)
491 obj.group_name, new_name)
492 obj.group_name = new_name
492 obj.group_name = new_name
493 elif isinstance(obj, Repository):
493 elif isinstance(obj, Repository):
494 # we need to get all repositories from this new group and
494 # we need to get all repositories from this new group and
495 # rename them accordingly to new group path
495 # rename them accordingly to new group path
496 new_name = obj.get_new_name(obj.just_name)
496 new_name = obj.get_new_name(obj.just_name)
497 log.debug('Fixing repo %s to new name %s',
497 log.debug('Fixing repo %s to new name %s',
498 obj.repo_name, new_name)
498 obj.repo_name, new_name)
499 obj.repo_name = new_name
499 obj.repo_name = new_name
500 self.sa.add(obj)
500 self.sa.add(obj)
501
501
502 self._rename_group(old_path, new_path)
502 self._rename_group(old_path, new_path)
503
503
504 # Trigger update event.
504 # Trigger update event.
505 events.trigger(events.RepoGroupUpdateEvent(repo_group))
505 events.trigger(events.RepoGroupUpdateEvent(repo_group))
506
506
507 return repo_group
507 return repo_group
508 except Exception:
508 except Exception:
509 log.error(traceback.format_exc())
509 log.error(traceback.format_exc())
510 raise
510 raise
511
511
512 def delete(self, repo_group, force_delete=False, fs_remove=True):
512 def delete(self, repo_group, force_delete=False, fs_remove=True):
513 repo_group = self._get_repo_group(repo_group)
513 repo_group = self._get_repo_group(repo_group)
514 if not repo_group:
514 if not repo_group:
515 return False
515 return False
516 try:
516 try:
517 self.sa.delete(repo_group)
517 self.sa.delete(repo_group)
518 if fs_remove:
518 if fs_remove:
519 self._delete_filesystem_group(repo_group, force_delete)
519 self._delete_filesystem_group(repo_group, force_delete)
520 else:
520 else:
521 log.debug('skipping removal from filesystem')
521 log.debug('skipping removal from filesystem')
522
522
523 # Trigger delete event.
523 # Trigger delete event.
524 events.trigger(events.RepoGroupDeleteEvent(repo_group))
524 events.trigger(events.RepoGroupDeleteEvent(repo_group))
525 return True
525 return True
526
526
527 except Exception:
527 except Exception:
528 log.error('Error removing repo_group %s', repo_group)
528 log.error('Error removing repo_group %s', repo_group)
529 raise
529 raise
530
530
531 def grant_user_permission(self, repo_group, user, perm):
531 def grant_user_permission(self, repo_group, user, perm):
532 """
532 """
533 Grant permission for user on given repository group, or update
533 Grant permission for user on given repository group, or update
534 existing one if found
534 existing one if found
535
535
536 :param repo_group: Instance of RepoGroup, repositories_group_id,
536 :param repo_group: Instance of RepoGroup, repositories_group_id,
537 or repositories_group name
537 or repositories_group name
538 :param user: Instance of User, user_id or username
538 :param user: Instance of User, user_id or username
539 :param perm: Instance of Permission, or permission_name
539 :param perm: Instance of Permission, or permission_name
540 """
540 """
541
541
542 repo_group = self._get_repo_group(repo_group)
542 repo_group = self._get_repo_group(repo_group)
543 user = self._get_user(user)
543 user = self._get_user(user)
544 permission = self._get_perm(perm)
544 permission = self._get_perm(perm)
545
545
546 # check if we have that permission already
546 # check if we have that permission already
547 obj = self.sa.query(UserRepoGroupToPerm)\
547 obj = self.sa.query(UserRepoGroupToPerm)\
548 .filter(UserRepoGroupToPerm.user == user)\
548 .filter(UserRepoGroupToPerm.user == user)\
549 .filter(UserRepoGroupToPerm.group == repo_group)\
549 .filter(UserRepoGroupToPerm.group == repo_group)\
550 .scalar()
550 .scalar()
551 if obj is None:
551 if obj is None:
552 # create new !
552 # create new !
553 obj = UserRepoGroupToPerm()
553 obj = UserRepoGroupToPerm()
554 obj.group = repo_group
554 obj.group = repo_group
555 obj.user = user
555 obj.user = user
556 obj.permission = permission
556 obj.permission = permission
557 self.sa.add(obj)
557 self.sa.add(obj)
558 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
558 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
559 action_logger_generic(
559 action_logger_generic(
560 'granted permission: {} to user: {} on repogroup: {}'.format(
560 'granted permission: {} to user: {} on repogroup: {}'.format(
561 perm, user, repo_group), namespace='security.repogroup')
561 perm, user, repo_group), namespace='security.repogroup')
562 return obj
562 return obj
563
563
564 def revoke_user_permission(self, repo_group, user):
564 def revoke_user_permission(self, repo_group, user):
565 """
565 """
566 Revoke permission for user on given repository group
566 Revoke permission for user on given repository group
567
567
568 :param repo_group: Instance of RepoGroup, repositories_group_id,
568 :param repo_group: Instance of RepoGroup, repositories_group_id,
569 or repositories_group name
569 or repositories_group name
570 :param user: Instance of User, user_id or username
570 :param user: Instance of User, user_id or username
571 """
571 """
572
572
573 repo_group = self._get_repo_group(repo_group)
573 repo_group = self._get_repo_group(repo_group)
574 user = self._get_user(user)
574 user = self._get_user(user)
575
575
576 obj = self.sa.query(UserRepoGroupToPerm)\
576 obj = self.sa.query(UserRepoGroupToPerm)\
577 .filter(UserRepoGroupToPerm.user == user)\
577 .filter(UserRepoGroupToPerm.user == user)\
578 .filter(UserRepoGroupToPerm.group == repo_group)\
578 .filter(UserRepoGroupToPerm.group == repo_group)\
579 .scalar()
579 .scalar()
580 if obj:
580 if obj:
581 self.sa.delete(obj)
581 self.sa.delete(obj)
582 log.debug('Revoked perm on %s on %s', repo_group, user)
582 log.debug('Revoked perm on %s on %s', repo_group, user)
583 action_logger_generic(
583 action_logger_generic(
584 'revoked permission from user: {} on repogroup: {}'.format(
584 'revoked permission from user: {} on repogroup: {}'.format(
585 user, repo_group), namespace='security.repogroup')
585 user, repo_group), namespace='security.repogroup')
586
586
587 def grant_user_group_permission(self, repo_group, group_name, perm):
587 def grant_user_group_permission(self, repo_group, group_name, perm):
588 """
588 """
589 Grant permission for user group on given repository group, or update
589 Grant permission for user group on given repository group, or update
590 existing one if found
590 existing one if found
591
591
592 :param repo_group: Instance of RepoGroup, repositories_group_id,
592 :param repo_group: Instance of RepoGroup, repositories_group_id,
593 or repositories_group name
593 or repositories_group name
594 :param group_name: Instance of UserGroup, users_group_id,
594 :param group_name: Instance of UserGroup, users_group_id,
595 or user group name
595 or user group name
596 :param perm: Instance of Permission, or permission_name
596 :param perm: Instance of Permission, or permission_name
597 """
597 """
598 repo_group = self._get_repo_group(repo_group)
598 repo_group = self._get_repo_group(repo_group)
599 group_name = self._get_user_group(group_name)
599 group_name = self._get_user_group(group_name)
600 permission = self._get_perm(perm)
600 permission = self._get_perm(perm)
601
601
602 # check if we have that permission already
602 # check if we have that permission already
603 obj = self.sa.query(UserGroupRepoGroupToPerm)\
603 obj = self.sa.query(UserGroupRepoGroupToPerm)\
604 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
604 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
605 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
605 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
606 .scalar()
606 .scalar()
607
607
608 if obj is None:
608 if obj is None:
609 # create new
609 # create new
610 obj = UserGroupRepoGroupToPerm()
610 obj = UserGroupRepoGroupToPerm()
611
611
612 obj.group = repo_group
612 obj.group = repo_group
613 obj.users_group = group_name
613 obj.users_group = group_name
614 obj.permission = permission
614 obj.permission = permission
615 self.sa.add(obj)
615 self.sa.add(obj)
616 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
616 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
617 action_logger_generic(
617 action_logger_generic(
618 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
618 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
619 perm, group_name, repo_group), namespace='security.repogroup')
619 perm, group_name, repo_group), namespace='security.repogroup')
620 return obj
620 return obj
621
621
622 def revoke_user_group_permission(self, repo_group, group_name):
622 def revoke_user_group_permission(self, repo_group, group_name):
623 """
623 """
624 Revoke permission for user group on given repository group
624 Revoke permission for user group on given repository group
625
625
626 :param repo_group: Instance of RepoGroup, repositories_group_id,
626 :param repo_group: Instance of RepoGroup, repositories_group_id,
627 or repositories_group name
627 or repositories_group name
628 :param group_name: Instance of UserGroup, users_group_id,
628 :param group_name: Instance of UserGroup, users_group_id,
629 or user group name
629 or user group name
630 """
630 """
631 repo_group = self._get_repo_group(repo_group)
631 repo_group = self._get_repo_group(repo_group)
632 group_name = self._get_user_group(group_name)
632 group_name = self._get_user_group(group_name)
633
633
634 obj = self.sa.query(UserGroupRepoGroupToPerm)\
634 obj = self.sa.query(UserGroupRepoGroupToPerm)\
635 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
635 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
636 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
636 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
637 .scalar()
637 .scalar()
638 if obj:
638 if obj:
639 self.sa.delete(obj)
639 self.sa.delete(obj)
640 log.debug('Revoked perm to %s on %s', repo_group, group_name)
640 log.debug('Revoked perm to %s on %s', repo_group, group_name)
641 action_logger_generic(
641 action_logger_generic(
642 'revoked permission from usergroup: {} on repogroup: {}'.format(
642 'revoked permission from usergroup: {} on repogroup: {}'.format(
643 group_name, repo_group), namespace='security.repogroup')
643 group_name, repo_group), namespace='security.repogroup')
644
644
645 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
645 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
646 super_user_actions=False):
646 super_user_actions=False):
647
647
648 from rhodecode.lib.utils import PartialRenderer
648 from rhodecode.lib.utils import PartialRenderer
649 _render = PartialRenderer('data_table/_dt_elements.html')
649 _render = PartialRenderer('data_table/_dt_elements.html')
650 c = _render.c
650 c = _render.c
651 h = _render.h
651 h = _render.h
652
652
653 def quick_menu(repo_group_name):
653 def quick_menu(repo_group_name):
654 return _render('quick_repo_group_menu', repo_group_name)
654 return _render('quick_repo_group_menu', repo_group_name)
655
655
656 def repo_group_lnk(repo_group_name):
656 def repo_group_lnk(repo_group_name):
657 return _render('repo_group_name', repo_group_name)
657 return _render('repo_group_name', repo_group_name)
658
658
659 def desc(desc):
659 def desc(desc, personal):
660 prefix = h.escaped_stylize(u'[personal] ') if personal else ''
661
660 if c.visual.stylify_metatags:
662 if c.visual.stylify_metatags:
661 return h.urlify_text(h.escaped_stylize(h.truncate(desc, 60)))
663 desc = h.urlify_text(prefix + h.escaped_stylize(desc))
662 else:
664 else:
663 return h.urlify_text(h.html_escape(h.truncate(desc, 60)))
665 desc = h.urlify_text(prefix + h.html_escape(desc))
666
667 return _render('repo_group_desc', desc)
664
668
665 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
669 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
666 return _render(
670 return _render(
667 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
671 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
668
672
669 def repo_group_name(repo_group_name, children_groups):
673 def repo_group_name(repo_group_name, children_groups):
670 return _render("repo_group_name", repo_group_name, children_groups)
674 return _render("repo_group_name", repo_group_name, children_groups)
671
675
672 def user_profile(username):
676 def user_profile(username):
673 return _render('user_profile', username)
677 return _render('user_profile', username)
674
678
675 repo_group_data = []
679 repo_group_data = []
676 for group in repo_group_list:
680 for group in repo_group_list:
677
681
678 row = {
682 row = {
679 "menu": quick_menu(group.group_name),
683 "menu": quick_menu(group.group_name),
680 "name": repo_group_lnk(group.group_name),
684 "name": repo_group_lnk(group.group_name),
681 "name_raw": group.group_name,
685 "name_raw": group.group_name,
682 "desc": desc(group.group_description),
686 "desc": desc(group.group_description, group.personal),
683 "top_level_repos": 0,
687 "top_level_repos": 0,
684 "owner": user_profile(group.user.username)
688 "owner": user_profile(group.user.username)
685 }
689 }
686 if admin:
690 if admin:
687 repo_count = group.repositories.count()
691 repo_count = group.repositories.count()
688 children_groups = map(
692 children_groups = map(
689 h.safe_unicode,
693 h.safe_unicode,
690 itertools.chain((g.name for g in group.parents),
694 itertools.chain((g.name for g in group.parents),
691 (x.name for x in [group])))
695 (x.name for x in [group])))
692 row.update({
696 row.update({
693 "action": repo_group_actions(
697 "action": repo_group_actions(
694 group.group_id, group.group_name, repo_count),
698 group.group_id, group.group_name, repo_count),
695 "top_level_repos": repo_count,
699 "top_level_repos": repo_count,
696 "name": repo_group_name(group.group_name, children_groups),
700 "name": repo_group_name(group.group_name, children_groups),
697
701
698 })
702 })
699 repo_group_data.append(row)
703 repo_group_data.append(row)
700
704
701 return repo_group_data
705 return repo_group_data
@@ -1,523 +1,530 b''
1
1
2 // tables.less
2 // tables.less
3 // For use in RhodeCode application tables;
3 // For use in RhodeCode application tables;
4 // see style guide documentation for guidelines.
4 // see style guide documentation for guidelines.
5
5
6 // TABLES
6 // TABLES
7
7
8 .rctable,
8 .rctable,
9 table.rctable,
9 table.rctable,
10 table.dataTable {
10 table.dataTable {
11 clear:both;
11 clear:both;
12 width: 100%;
12 width: 100%;
13 margin: 0 auto @padding;
13 margin: 0 auto @padding;
14 padding: 0;
14 padding: 0;
15 vertical-align: baseline;
15 vertical-align: baseline;
16 line-height:1.5em;
16 line-height:1.5em;
17 border: none;
17 border: none;
18 outline: none;
18 outline: none;
19 border-collapse: collapse;
19 border-collapse: collapse;
20 border-spacing: 0;
20 border-spacing: 0;
21 color: @grey2;
21 color: @grey2;
22
22
23 b {
23 b {
24 font-weight: normal;
24 font-weight: normal;
25 }
25 }
26
26
27 em {
27 em {
28 font-weight: bold;
28 font-weight: bold;
29 font-style: normal;
29 font-style: normal;
30 }
30 }
31
31
32 th,
32 th,
33 td {
33 td {
34 height: auto;
34 height: auto;
35 max-width: 20%;
35 max-width: 20%;
36 padding: .65em 1em .65em 0;
36 padding: .65em 1em .65em 0;
37 vertical-align: middle;
37 vertical-align: middle;
38 border-bottom: @border-thickness solid @grey5;
38 border-bottom: @border-thickness solid @grey5;
39 white-space: normal;
39 white-space: normal;
40
40
41 &.td-radio,
41 &.td-radio,
42 &.td-checkbox {
42 &.td-checkbox {
43 padding-right: 0;
43 padding-right: 0;
44 text-align: center;
44 text-align: center;
45
45
46 input {
46 input {
47 margin: 0 1em;
47 margin: 0 1em;
48 }
48 }
49 }
49 }
50
50
51 &.truncate-wrap {
51 &.truncate-wrap {
52 white-space: nowrap !important;
52 white-space: nowrap !important;
53 }
53 }
54
54
55 pre {
55 pre {
56 margin: 0;
56 margin: 0;
57 }
57 }
58
58
59 .show_more {
59 .show_more {
60 height: inherit;
60 height: inherit;
61 }
61 }
62 }
62 }
63
63
64 .expired td {
64 .expired td {
65 background-color: @grey7;
65 background-color: @grey7;
66 }
66 }
67
67
68 .td-radio + .td-owner {
68 .td-radio + .td-owner {
69 padding-left: 1em;
69 padding-left: 1em;
70 }
70 }
71
71
72
72
73 th {
73 th {
74 text-align: left;
74 text-align: left;
75 font-family: @text-semibold;
75 font-family: @text-semibold;
76 }
76 }
77
77
78 .hl {
78 .hl {
79 td {
79 td {
80 background-color: lighten(@alert4,25%);
80 background-color: lighten(@alert4,25%);
81 }
81 }
82 }
82 }
83
83
84 // Special Data Cell Types
84 // Special Data Cell Types
85 // See style guide for desciptions and examples.
85 // See style guide for desciptions and examples.
86
86
87 td {
87 td {
88
88
89 &.user {
89 &.user {
90 padding-left: 1em;
90 padding-left: 1em;
91 }
91 }
92
92
93 &.td-rss {
93 &.td-rss {
94 width: 20px;
94 width: 20px;
95 min-width: 0;
95 min-width: 0;
96 margin: 0;
96 margin: 0;
97 }
97 }
98
98
99 &.quick_repo_menu {
99 &.quick_repo_menu {
100 width: 15px;
100 width: 15px;
101 text-align: center;
101 text-align: center;
102
102
103 &:hover {
103 &:hover {
104 background-color: @grey5;
104 background-color: @grey5;
105 }
105 }
106 }
106 }
107
107
108 &.td-hash {
108 &.td-hash {
109 min-width: 80px;
109 min-width: 80px;
110 width: 200px;
110 width: 200px;
111 }
111 }
112
112
113 &.td-time {
113 &.td-time {
114 width: 160px;
114 width: 160px;
115 white-space: nowrap;
115 white-space: nowrap;
116 }
116 }
117
117
118 &.annotate{
118 &.annotate{
119 padding-right: 0;
119 padding-right: 0;
120
120
121 div.annotatediv{
121 div.annotatediv{
122 margin: 0 0.7em;
122 margin: 0 0.7em;
123 }
123 }
124 }
124 }
125
125
126 &.tags-col {
126 &.tags-col {
127 padding-right: 0;
127 padding-right: 0;
128 }
128 }
129
129
130 &.td-description {
130 &.td-description {
131 min-width: 350px;
131 min-width: 350px;
132
133 &.truncate, .truncate-wrap {
134 white-space: nowrap;
135 overflow: hidden;
136 text-overflow: ellipsis;
137 max-width: 450px;
138 }
132 }
139 }
133
140
134 &.td-componentname {
141 &.td-componentname {
135 white-space: nowrap;
142 white-space: nowrap;
136 }
143 }
137
144
138 &.td-journalaction {
145 &.td-journalaction {
139 min-width: 300px;
146 min-width: 300px;
140
147
141 .journal_action_params {
148 .journal_action_params {
142 // waiting for feedback
149 // waiting for feedback
143 }
150 }
144 }
151 }
145
152
146 &.td-active {
153 &.td-active {
147 padding-left: .65em;
154 padding-left: .65em;
148 }
155 }
149
156
150 &.td-url {
157 &.td-url {
151 white-space: nowrap;
158 white-space: nowrap;
152 }
159 }
153
160
154 &.td-comments {
161 &.td-comments {
155 min-width: 3em;
162 min-width: 3em;
156 }
163 }
157
164
158 &.td-buttons {
165 &.td-buttons {
159 padding: .3em 0;
166 padding: .3em 0;
160 }
167 }
161
168
162 &.td-action {
169 &.td-action {
163 // this is for the remove/delete/edit buttons
170 // this is for the remove/delete/edit buttons
164 padding-right: 0;
171 padding-right: 0;
165 min-width: 95px;
172 min-width: 95px;
166 text-transform: capitalize;
173 text-transform: capitalize;
167
174
168 i {
175 i {
169 display: none;
176 display: none;
170 }
177 }
171 }
178 }
172
179
173 // TODO: lisa: this needs to be cleaned up with the buttons
180 // TODO: lisa: this needs to be cleaned up with the buttons
174 .grid_edit,
181 .grid_edit,
175 .grid_delete {
182 .grid_delete {
176 display: inline-block;
183 display: inline-block;
177 margin: 0 @padding/3 0 0;
184 margin: 0 @padding/3 0 0;
178 font-family: @text-light;
185 font-family: @text-light;
179
186
180 i {
187 i {
181 display: none;
188 display: none;
182 }
189 }
183 }
190 }
184
191
185 .grid_edit + .grid_delete {
192 .grid_edit + .grid_delete {
186 border-left: @border-thickness solid @grey5;
193 border-left: @border-thickness solid @grey5;
187 padding-left: @padding/2;
194 padding-left: @padding/2;
188 }
195 }
189
196
190 &.td-compare {
197 &.td-compare {
191
198
192 input {
199 input {
193 margin-right: 1em;
200 margin-right: 1em;
194 }
201 }
195
202
196 .compare-radio-button {
203 .compare-radio-button {
197 margin: 0 1em 0 0;
204 margin: 0 1em 0 0;
198 }
205 }
199
206
200
207
201 }
208 }
202
209
203 &.td-tags {
210 &.td-tags {
204 padding: .5em 1em .5em 0;
211 padding: .5em 1em .5em 0;
205 width: 140px;
212 width: 140px;
206
213
207 .tag {
214 .tag {
208 margin: 1px;
215 margin: 1px;
209 float: left;
216 float: left;
210 }
217 }
211 }
218 }
212
219
213 .icon-svn, .icon-hg, .icon-git {
220 .icon-svn, .icon-hg, .icon-git {
214 font-size: 1.4em;
221 font-size: 1.4em;
215 }
222 }
216
223
217 &.collapse_commit,
224 &.collapse_commit,
218 &.expand_commit {
225 &.expand_commit {
219 padding-right: 0;
226 padding-right: 0;
220 padding-left: 1em;
227 padding-left: 1em;
221 }
228 }
222 }
229 }
223
230
224 .perm_admin_row {
231 .perm_admin_row {
225 color: @grey4;
232 color: @grey4;
226 background-color: @grey6;
233 background-color: @grey6;
227 }
234 }
228
235
229 .noborder {
236 .noborder {
230 border: none;
237 border: none;
231
238
232 td {
239 td {
233 border: none;
240 border: none;
234 }
241 }
235 }
242 }
236 }
243 }
237
244
238 // TRUNCATING
245 // TRUNCATING
239 // TODO: lisaq: should this possibly be moved out of tables.less?
246 // TODO: lisaq: should this possibly be moved out of tables.less?
240 // for truncated text
247 // for truncated text
241 // used inside of table cells and in code block headers
248 // used inside of table cells and in code block headers
242 .truncate-wrap {
249 .truncate-wrap {
243 white-space: nowrap !important;
250 white-space: nowrap !important;
244
251
245 //truncated text
252 //truncated text
246 .truncate {
253 .truncate {
247 max-width: 450px;
254 max-width: 450px;
248 width: 300px;
255 width: 300px;
249 overflow: hidden;
256 overflow: hidden;
250 text-overflow: ellipsis;
257 text-overflow: ellipsis;
251 -o-text-overflow: ellipsis;
258 -o-text-overflow: ellipsis;
252 -ms-text-overflow: ellipsis;
259 -ms-text-overflow: ellipsis;
253
260
254 &.autoexpand {
261 &.autoexpand {
255 width: 120px;
262 width: 120px;
256 margin-right: 200px;
263 margin-right: 200px;
257 }
264 }
258 }
265 }
259 &:hover .truncate.autoexpand {
266 &:hover .truncate.autoexpand {
260 overflow: visible;
267 overflow: visible;
261 }
268 }
262
269
263 .tags-truncate {
270 .tags-truncate {
264 width: 150px;
271 width: 150px;
265 height: 22px;
272 height: 22px;
266 overflow: hidden;
273 overflow: hidden;
267
274
268 .tag {
275 .tag {
269 display: inline-block;
276 display: inline-block;
270 }
277 }
271
278
272 &.truncate {
279 &.truncate {
273 height: 22px;
280 height: 22px;
274 max-height:2em;
281 max-height:2em;
275 width: 140px;
282 width: 140px;
276 }
283 }
277 }
284 }
278 }
285 }
279
286
280 .apikeys_wrap {
287 .apikeys_wrap {
281 margin-bottom: @padding;
288 margin-bottom: @padding;
282
289
283 table.rctable td:first-child {
290 table.rctable td:first-child {
284 width: 340px;
291 width: 340px;
285 }
292 }
286 }
293 }
287
294
288
295
289
296
290 // SPECIAL CASES
297 // SPECIAL CASES
291
298
292 // Repository Followers
299 // Repository Followers
293 table.rctable.followers_data {
300 table.rctable.followers_data {
294 width: 75%;
301 width: 75%;
295 margin: 0;
302 margin: 0;
296 }
303 }
297
304
298 // Repository List
305 // Repository List
299 // Group Members List
306 // Group Members List
300 table.rctable.group_members,
307 table.rctable.group_members,
301 table#repo_list_table {
308 table#repo_list_table {
302 min-width: 600px;
309 min-width: 600px;
303 }
310 }
304
311
305 // Keyboard mappings
312 // Keyboard mappings
306 table.keyboard-mappings {
313 table.keyboard-mappings {
307 th {
314 th {
308 text-align: left;
315 text-align: left;
309 font-family: @text-semibold;
316 font-family: @text-semibold;
310 }
317 }
311 }
318 }
312
319
313 // Branches, Tags, and Bookmarks
320 // Branches, Tags, and Bookmarks
314 #obj_list_table.dataTable {
321 #obj_list_table.dataTable {
315 td.td-time {
322 td.td-time {
316 padding-right: 1em;
323 padding-right: 1em;
317 }
324 }
318 }
325 }
319
326
320 // User Admin
327 // User Admin
321 .rctable.useremails,
328 .rctable.useremails,
322 .rctable.account_emails {
329 .rctable.account_emails {
323 .tag,
330 .tag,
324 .btn {
331 .btn {
325 float: right;
332 float: right;
326 }
333 }
327 .btn { //to line up with tags
334 .btn { //to line up with tags
328 margin-right: 1.65em;
335 margin-right: 1.65em;
329 }
336 }
330 }
337 }
331
338
332 // User List
339 // User List
333 #user_list_table {
340 #user_list_table {
334
341
335 td.td-user {
342 td.td-user {
336 min-width: 100px;
343 min-width: 100px;
337 }
344 }
338 }
345 }
339
346
340 // Pull Request List Table
347 // Pull Request List Table
341 #pull_request_list_table.dataTable {
348 #pull_request_list_table.dataTable {
342
349
343 //TODO: lisa: This needs to be removed once the description is adjusted
350 //TODO: lisa: This needs to be removed once the description is adjusted
344 // for using an expand_commit button (see issue 765)
351 // for using an expand_commit button (see issue 765)
345 td {
352 td {
346 vertical-align: middle;
353 vertical-align: middle;
347 }
354 }
348 }
355 }
349
356
350 // Settings (no border)
357 // Settings (no border)
351 table.rctable.dl-settings {
358 table.rctable.dl-settings {
352 td {
359 td {
353 border: none;
360 border: none;
354 }
361 }
355 }
362 }
356
363
357
364
358 // Statistics
365 // Statistics
359 table.trending_language_tbl {
366 table.trending_language_tbl {
360 width: 100%;
367 width: 100%;
361 line-height: 1em;
368 line-height: 1em;
362
369
363 td div {
370 td div {
364 overflow: visible;
371 overflow: visible;
365 }
372 }
366 }
373 }
367
374
368 .trending_language_tbl, .trending_language_tbl td {
375 .trending_language_tbl, .trending_language_tbl td {
369 border: 0;
376 border: 0;
370 margin: 0;
377 margin: 0;
371 padding: 0;
378 padding: 0;
372 background: transparent;
379 background: transparent;
373 }
380 }
374
381
375 .trending_language_tbl, .trending_language_tbl tr {
382 .trending_language_tbl, .trending_language_tbl tr {
376 border-spacing: 0 3px;
383 border-spacing: 0 3px;
377 }
384 }
378
385
379 .trending_language {
386 .trending_language {
380 position: relative;
387 position: relative;
381 width: 100%;
388 width: 100%;
382 height: 19px;
389 height: 19px;
383 overflow: hidden;
390 overflow: hidden;
384 background-color: @grey6;
391 background-color: @grey6;
385
392
386 span, b{
393 span, b{
387 position: absolute;
394 position: absolute;
388 display: block;
395 display: block;
389 height: 12px;
396 height: 12px;
390 margin-bottom: 0px;
397 margin-bottom: 0px;
391 white-space: pre;
398 white-space: pre;
392 padding: floor(@basefontsize/4);
399 padding: floor(@basefontsize/4);
393 top: 0;
400 top: 0;
394 left: 0;
401 left: 0;
395 }
402 }
396
403
397 span{
404 span{
398 color: @text-color;
405 color: @text-color;
399 z-index: 0;
406 z-index: 0;
400 min-width: 20px;
407 min-width: 20px;
401 }
408 }
402
409
403 b {
410 b {
404 z-index: 1;
411 z-index: 1;
405 overflow: hidden;
412 overflow: hidden;
406 background-color: @rcblue;
413 background-color: @rcblue;
407 color: #FFF;
414 color: #FFF;
408 text-decoration: none;
415 text-decoration: none;
409 }
416 }
410
417
411 }
418 }
412
419
413 // Changesets
420 // Changesets
414 #changesets.rctable {
421 #changesets.rctable {
415
422
416 // td must be fixed height for graph
423 // td must be fixed height for graph
417 td {
424 td {
418 height: 32px;
425 height: 32px;
419 padding: 0 1em 0 0;
426 padding: 0 1em 0 0;
420 vertical-align: middle;
427 vertical-align: middle;
421 white-space: nowrap;
428 white-space: nowrap;
422
429
423 &.td-description {
430 &.td-description {
424 white-space: normal;
431 white-space: normal;
425 }
432 }
426
433
427 &.expand_commit {
434 &.expand_commit {
428 padding-right: 0;
435 padding-right: 0;
429 }
436 }
430 }
437 }
431 }
438 }
432
439
433 // Compare
440 // Compare
434 table.compare_view_commits {
441 table.compare_view_commits {
435 margin-top: @space;
442 margin-top: @space;
436
443
437 td.td-time {
444 td.td-time {
438 padding-left: .5em;
445 padding-left: .5em;
439 }
446 }
440
447
441 tr:hover {
448 tr:hover {
442 cursor: pointer;
449 cursor: pointer;
443
450
444 td {
451 td {
445 background-color: lighten(@alert4,25%);
452 background-color: lighten(@alert4,25%);
446 }
453 }
447 }
454 }
448 }
455 }
449
456
450 .file_history {
457 .file_history {
451 td.td-actions {
458 td.td-actions {
452 text-align: right;
459 text-align: right;
453 }
460 }
454 }
461 }
455
462
456 .compare_view_files {
463 .compare_view_files {
457
464
458 td.td-actions {
465 td.td-actions {
459 text-align: right;
466 text-align: right;
460 }
467 }
461
468
462 .flag_status {
469 .flag_status {
463 margin: 0 0 0 5px;
470 margin: 0 0 0 5px;
464 }
471 }
465
472
466 td.injected_diff {
473 td.injected_diff {
467
474
468 .code-difftable {
475 .code-difftable {
469 border:none;
476 border:none;
470 }
477 }
471
478
472 .diff-container {
479 .diff-container {
473 border: @border-thickness solid @border-default-color;
480 border: @border-thickness solid @border-default-color;
474 .border-radius(@border-radius);
481 .border-radius(@border-radius);
475 }
482 }
476
483
477 div.diffblock {
484 div.diffblock {
478 border:none;
485 border:none;
479 }
486 }
480
487
481 div.code-body {
488 div.code-body {
482 max-width: 1152px;
489 max-width: 1152px;
483 }
490 }
484 }
491 }
485
492
486 .rctable {
493 .rctable {
487
494
488 td {
495 td {
489 padding-top: @space;
496 padding-top: @space;
490 }
497 }
491
498
492 &:first-child td {
499 &:first-child td {
493 padding-top: 0;
500 padding-top: 0;
494 }
501 }
495 }
502 }
496
503
497 .comment-bubble,
504 .comment-bubble,
498 .show_comments {
505 .show_comments {
499 float: right;
506 float: right;
500 visibility: hidden;
507 visibility: hidden;
501 padding: 0 1em 0 0;
508 padding: 0 1em 0 0;
502 }
509 }
503
510
504 .injected_diff {
511 .injected_diff {
505 padding-bottom: @padding;
512 padding-bottom: @padding;
506 }
513 }
507 }
514 }
508
515
509 // Gist List
516 // Gist List
510 #gist_list_table {
517 #gist_list_table {
511 td {
518 td {
512 vertical-align: middle;
519 vertical-align: middle;
513
520
514 div{
521 div{
515 display: inline-block;
522 display: inline-block;
516 vertical-align: middle;
523 vertical-align: middle;
517 }
524 }
518
525
519 img{
526 img{
520 vertical-align: middle;
527 vertical-align: middle;
521 }
528 }
522 }
529 }
523 }
530 }
@@ -1,309 +1,317 b''
1 ## DATA TABLE RE USABLE ELEMENTS
1 ## DATA TABLE RE USABLE ELEMENTS
2 ## usage:
2 ## usage:
3 ## <%namespace name="dt" file="/data_table/_dt_elements.html"/>
3 ## <%namespace name="dt" file="/data_table/_dt_elements.html"/>
4 <%namespace name="base" file="/base/base.html"/>
4 <%namespace name="base" file="/base/base.html"/>
5
5
6 ## REPOSITORY RENDERERS
6 ## REPOSITORY RENDERERS
7 <%def name="quick_menu(repo_name)">
7 <%def name="quick_menu(repo_name)">
8 <i class="pointer icon-more"></i>
8 <i class="pointer icon-more"></i>
9 <div class="menu_items_container hidden">
9 <div class="menu_items_container hidden">
10 <ul class="menu_items">
10 <ul class="menu_items">
11 <li>
11 <li>
12 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
12 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
13 <span>${_('Summary')}</span>
13 <span>${_('Summary')}</span>
14 </a>
14 </a>
15 </li>
15 </li>
16 <li>
16 <li>
17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
18 <span>${_('Changelog')}</span>
18 <span>${_('Changelog')}</span>
19 </a>
19 </a>
20 </li>
20 </li>
21 <li>
21 <li>
22 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
22 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
23 <span>${_('Files')}</span>
23 <span>${_('Files')}</span>
24 </a>
24 </a>
25 </li>
25 </li>
26 <li>
26 <li>
27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
28 <span>${_('Fork')}</span>
28 <span>${_('Fork')}</span>
29 </a>
29 </a>
30 </li>
30 </li>
31 </ul>
31 </ul>
32 </div>
32 </div>
33 </%def>
33 </%def>
34
34
35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
36 <%
36 <%
37 def get_name(name,short_name=short_name):
37 def get_name(name,short_name=short_name):
38 if short_name:
38 if short_name:
39 return name.split('/')[-1]
39 return name.split('/')[-1]
40 else:
40 else:
41 return name
41 return name
42 %>
42 %>
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 ##NAME
44 ##NAME
45 <a href="${h.url('edit_repo' if admin else 'summary_home',repo_name=name)}">
45 <a href="${h.url('edit_repo' if admin else 'summary_home',repo_name=name)}">
46
46
47 ##TYPE OF REPO
47 ##TYPE OF REPO
48 %if h.is_hg(rtype):
48 %if h.is_hg(rtype):
49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
50 %elif h.is_git(rtype):
50 %elif h.is_git(rtype):
51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
52 %elif h.is_svn(rtype):
52 %elif h.is_svn(rtype):
53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
54 %endif
54 %endif
55
55
56 ##PRIVATE/PUBLIC
56 ##PRIVATE/PUBLIC
57 %if private and c.visual.show_private_icon:
57 %if private and c.visual.show_private_icon:
58 <i class="icon-lock" title="${_('Private repository')}"></i>
58 <i class="icon-lock" title="${_('Private repository')}"></i>
59 %elif not private and c.visual.show_public_icon:
59 %elif not private and c.visual.show_public_icon:
60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
61 %else:
61 %else:
62 <span></span>
62 <span></span>
63 %endif
63 %endif
64 ${get_name(name)}
64 ${get_name(name)}
65 </a>
65 </a>
66 %if fork_of:
66 %if fork_of:
67 <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
67 <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
68 %endif
68 %endif
69 %if rstate == 'repo_state_pending':
69 %if rstate == 'repo_state_pending':
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
71 %endif
71 %endif
72 </div>
72 </div>
73 </%def>
73 </%def>
74
74
75 <%def name="repo_desc(description)">
76 <div class="truncate-wrap">${description}</div>
77 </%def>
78
75 <%def name="last_change(last_change)">
79 <%def name="last_change(last_change)">
76 ${h.age_component(last_change)}
80 ${h.age_component(last_change)}
77 </%def>
81 </%def>
78
82
79 <%def name="revision(name,rev,tip,author,last_msg)">
83 <%def name="revision(name,rev,tip,author,last_msg)">
80 <div>
84 <div>
81 %if rev >= 0:
85 %if rev >= 0:
82 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
86 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
83 %else:
87 %else:
84 ${_('No commits yet')}
88 ${_('No commits yet')}
85 %endif
89 %endif
86 </div>
90 </div>
87 </%def>
91 </%def>
88
92
89 <%def name="rss(name)">
93 <%def name="rss(name)">
90 %if c.rhodecode_user.username != h.DEFAULT_USER:
94 %if c.rhodecode_user.username != h.DEFAULT_USER:
91 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
95 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
92 %else:
96 %else:
93 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
97 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
94 %endif
98 %endif
95 </%def>
99 </%def>
96
100
97 <%def name="atom(name)">
101 <%def name="atom(name)">
98 %if c.rhodecode_user.username != h.DEFAULT_USER:
102 %if c.rhodecode_user.username != h.DEFAULT_USER:
99 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
103 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
100 %else:
104 %else:
101 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
105 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
102 %endif
106 %endif
103 </%def>
107 </%def>
104
108
105 <%def name="user_gravatar(email, size=16)">
109 <%def name="user_gravatar(email, size=16)">
106 <div class="rc-user tooltip" title="${h.author_string(email)}">
110 <div class="rc-user tooltip" title="${h.author_string(email)}">
107 ${base.gravatar(email, 16)}
111 ${base.gravatar(email, 16)}
108 </div>
112 </div>
109 </%def>
113 </%def>
110
114
111 <%def name="repo_actions(repo_name, super_user=True)">
115 <%def name="repo_actions(repo_name, super_user=True)">
112 <div>
116 <div>
113 <div class="grid_edit">
117 <div class="grid_edit">
114 <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
118 <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
115 <i class="icon-pencil"></i>Edit</a>
119 <i class="icon-pencil"></i>Edit</a>
116 </div>
120 </div>
117 <div class="grid_delete">
121 <div class="grid_delete">
118 ${h.secure_form(h.url('repo', repo_name=repo_name),method='delete')}
122 ${h.secure_form(h.url('repo', repo_name=repo_name),method='delete')}
119 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
120 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
121 ${h.end_form()}
125 ${h.end_form()}
122 </div>
126 </div>
123 </div>
127 </div>
124 </%def>
128 </%def>
125
129
126 <%def name="repo_state(repo_state)">
130 <%def name="repo_state(repo_state)">
127 <div>
131 <div>
128 %if repo_state == 'repo_state_pending':
132 %if repo_state == 'repo_state_pending':
129 <div class="tag tag4">${_('Creating')}</div>
133 <div class="tag tag4">${_('Creating')}</div>
130 %elif repo_state == 'repo_state_created':
134 %elif repo_state == 'repo_state_created':
131 <div class="tag tag1">${_('Created')}</div>
135 <div class="tag tag1">${_('Created')}</div>
132 %else:
136 %else:
133 <div class="tag alert2" title="${repo_state}">invalid</div>
137 <div class="tag alert2" title="${repo_state}">invalid</div>
134 %endif
138 %endif
135 </div>
139 </div>
136 </%def>
140 </%def>
137
141
138
142
139 ## REPO GROUP RENDERERS
143 ## REPO GROUP RENDERERS
140 <%def name="quick_repo_group_menu(repo_group_name)">
144 <%def name="quick_repo_group_menu(repo_group_name)">
141 <i class="pointer icon-more"></i>
145 <i class="pointer icon-more"></i>
142 <div class="menu_items_container hidden">
146 <div class="menu_items_container hidden">
143 <ul class="menu_items">
147 <ul class="menu_items">
144 <li>
148 <li>
145 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
149 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
146 <span class="icon">
150 <span class="icon">
147 <i class="icon-file-text"></i>
151 <i class="icon-file-text"></i>
148 </span>
152 </span>
149 <span>${_('Summary')}</span>
153 <span>${_('Summary')}</span>
150 </a>
154 </a>
151 </li>
155 </li>
152
156
153 </ul>
157 </ul>
154 </div>
158 </div>
155 </%def>
159 </%def>
156
160
157 <%def name="repo_group_name(repo_group_name, children_groups=None)">
161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
158 <div>
162 <div>
159 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
163 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
160 <i class="icon-folder-close" title="${_('Repository group')}"></i>
164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
161 %if children_groups:
165 %if children_groups:
162 ${h.literal(' &raquo; '.join(children_groups))}
166 ${h.literal(' &raquo; '.join(children_groups))}
163 %else:
167 %else:
164 ${repo_group_name}
168 ${repo_group_name}
165 %endif
169 %endif
166 </a>
170 </a>
167 </div>
171 </div>
168 </%def>
172 </%def>
169
173
174 <%def name="repo_group_desc(description)">
175 <div class="truncate-wrap">${description}</div>
176 </%def>
177
170 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
178 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
171 <div class="grid_edit">
179 <div class="grid_edit">
172 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
180 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
173 </div>
181 </div>
174 <div class="grid_delete">
182 <div class="grid_delete">
175 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
183 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
176 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
184 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
177 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
185 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
178 ${h.end_form()}
186 ${h.end_form()}
179 </div>
187 </div>
180 </%def>
188 </%def>
181
189
182
190
183 <%def name="user_actions(user_id, username)">
191 <%def name="user_actions(user_id, username)">
184 <div class="grid_edit">
192 <div class="grid_edit">
185 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
193 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
186 <i class="icon-pencil"></i>Edit</a>
194 <i class="icon-pencil"></i>Edit</a>
187 </div>
195 </div>
188 <div class="grid_delete">
196 <div class="grid_delete">
189 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
197 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
190 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
198 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
191 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
199 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
192 ${h.end_form()}
200 ${h.end_form()}
193 </div>
201 </div>
194 </%def>
202 </%def>
195
203
196 <%def name="user_group_actions(user_group_id, user_group_name)">
204 <%def name="user_group_actions(user_group_id, user_group_name)">
197 <div class="grid_edit">
205 <div class="grid_edit">
198 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
206 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
199 </div>
207 </div>
200 <div class="grid_delete">
208 <div class="grid_delete">
201 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
209 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
202 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
210 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
203 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
211 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
204 ${h.end_form()}
212 ${h.end_form()}
205 </div>
213 </div>
206 </%def>
214 </%def>
207
215
208
216
209 <%def name="user_name(user_id, username)">
217 <%def name="user_name(user_id, username)">
210 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
218 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
211 </%def>
219 </%def>
212
220
213 <%def name="user_profile(username)">
221 <%def name="user_profile(username)">
214 ${base.gravatar_with_user(username, 16)}
222 ${base.gravatar_with_user(username, 16)}
215 </%def>
223 </%def>
216
224
217 <%def name="user_group_name(user_group_id, user_group_name)">
225 <%def name="user_group_name(user_group_id, user_group_name)">
218 <div>
226 <div>
219 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
227 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
220 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
228 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
221 </div>
229 </div>
222 </%def>
230 </%def>
223
231
224
232
225 ## GISTS
233 ## GISTS
226
234
227 <%def name="gist_gravatar(full_contact)">
235 <%def name="gist_gravatar(full_contact)">
228 <div class="gist_gravatar">
236 <div class="gist_gravatar">
229 ${base.gravatar(full_contact, 30)}
237 ${base.gravatar(full_contact, 30)}
230 </div>
238 </div>
231 </%def>
239 </%def>
232
240
233 <%def name="gist_access_id(gist_access_id, full_contact)">
241 <%def name="gist_access_id(gist_access_id, full_contact)">
234 <div>
242 <div>
235 <b>
243 <b>
236 <a href="${h.url('gist',gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
244 <a href="${h.url('gist',gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
237 </b>
245 </b>
238 </div>
246 </div>
239 </%def>
247 </%def>
240
248
241 <%def name="gist_author(full_contact, created_on, expires)">
249 <%def name="gist_author(full_contact, created_on, expires)">
242 ${base.gravatar_with_user(full_contact, 16)}
250 ${base.gravatar_with_user(full_contact, 16)}
243 </%def>
251 </%def>
244
252
245
253
246 <%def name="gist_created(created_on)">
254 <%def name="gist_created(created_on)">
247 <div class="created">
255 <div class="created">
248 ${h.age_component(created_on, time_is_local=True)}
256 ${h.age_component(created_on, time_is_local=True)}
249 </div>
257 </div>
250 </%def>
258 </%def>
251
259
252 <%def name="gist_expires(expires)">
260 <%def name="gist_expires(expires)">
253 <div class="created">
261 <div class="created">
254 %if expires == -1:
262 %if expires == -1:
255 ${_('never')}
263 ${_('never')}
256 %else:
264 %else:
257 ${h.age_component(h.time_to_utcdatetime(expires))}
265 ${h.age_component(h.time_to_utcdatetime(expires))}
258 %endif
266 %endif
259 </div>
267 </div>
260 </%def>
268 </%def>
261
269
262 <%def name="gist_type(gist_type)">
270 <%def name="gist_type(gist_type)">
263 %if gist_type != 'public':
271 %if gist_type != 'public':
264 <div class="tag">${_('Private')}</div>
272 <div class="tag">${_('Private')}</div>
265 %endif
273 %endif
266 </%def>
274 </%def>
267
275
268 <%def name="gist_description(gist_description)">
276 <%def name="gist_description(gist_description)">
269 ${gist_description}
277 ${gist_description}
270 </%def>
278 </%def>
271
279
272
280
273 ## PULL REQUESTS GRID RENDERERS
281 ## PULL REQUESTS GRID RENDERERS
274
282
275 <%def name="pullrequest_target_repo(repo_name)">
283 <%def name="pullrequest_target_repo(repo_name)">
276 <div class="truncate">
284 <div class="truncate">
277 ${h.link_to(repo_name,h.url('summary_home',repo_name=repo_name))}
285 ${h.link_to(repo_name,h.url('summary_home',repo_name=repo_name))}
278 </div>
286 </div>
279 </%def>
287 </%def>
280 <%def name="pullrequest_status(status)">
288 <%def name="pullrequest_status(status)">
281 <div class="${'flag_status %s' % status} pull-left"></div>
289 <div class="${'flag_status %s' % status} pull-left"></div>
282 </%def>
290 </%def>
283
291
284 <%def name="pullrequest_title(title, description)">
292 <%def name="pullrequest_title(title, description)">
285 ${title} <br/>
293 ${title} <br/>
286 ${h.shorter(description, 40)}
294 ${h.shorter(description, 40)}
287 </%def>
295 </%def>
288
296
289 <%def name="pullrequest_comments(comments_nr)">
297 <%def name="pullrequest_comments(comments_nr)">
290 <i class="icon-comment icon-comment-colored"></i> ${comments_nr}
298 <i class="icon-comment icon-comment-colored"></i> ${comments_nr}
291 </%def>
299 </%def>
292
300
293 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
294 <a href="${h.url('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
302 <a href="${h.url('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
295 % if short:
303 % if short:
296 #${pull_request_id}
304 #${pull_request_id}
297 % else:
305 % else:
298 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
306 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
299 % endif
307 % endif
300 </a>
308 </a>
301 </%def>
309 </%def>
302
310
303 <%def name="pullrequest_updated_on(updated_on)">
311 <%def name="pullrequest_updated_on(updated_on)">
304 ${h.age_component(h.time_to_utcdatetime(updated_on))}
312 ${h.age_component(h.time_to_utcdatetime(updated_on))}
305 </%def>
313 </%def>
306
314
307 <%def name="pullrequest_author(full_contact)">
315 <%def name="pullrequest_author(full_contact)">
308 ${base.gravatar_with_user(full_contact, 16)}
316 ${base.gravatar_with_user(full_contact, 16)}
309 </%def>
317 </%def>
General Comments 0
You need to be logged in to leave comments. Login now