##// END OF EJS Templates
repository-groups: introduce last change for repository groups.
marcink -
r1940:04fca2a9 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,35 b''
1 import logging
2 import datetime
3
4 from sqlalchemy import *
5 from rhodecode.model import meta
6 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
7
8 log = logging.getLogger(__name__)
9
10
11 def upgrade(migrate_engine):
12 """
13 Upgrade operations go here.
14 Don't create your own engine; bind migrate_engine to your metadata
15 """
16 _reset_base(migrate_engine)
17 from rhodecode.lib.dbmigrate.schema import db_4_7_0_1 as db
18
19 repo_group_table = db.RepoGroup.__table__
20
21 updated_on = Column(
22 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
23 default=datetime.datetime.now)
24 updated_on.create(table=repo_group_table)
25
26 fixups(db, meta.Session)
27
28
29 def downgrade(migrate_engine):
30 meta = MetaData()
31 meta.bind = migrate_engine
32
33
34 def fixups(models, _SESSION):
35 pass
@@ -1,63 +1,63 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22
23 23 RhodeCode, a web based repository management software
24 24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 25 """
26 26
27 27 import os
28 28 import sys
29 29 import platform
30 30
31 31 VERSION = tuple(open(os.path.join(
32 32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33 33
34 34 BACKENDS = {
35 35 'hg': 'Mercurial repository',
36 36 'git': 'Git repository',
37 37 'svn': 'Subversion repository',
38 38 }
39 39
40 40 CELERY_ENABLED = False
41 41 CELERY_EAGER = False
42 42
43 43 # link to config for pylons
44 44 CONFIG = {}
45 45
46 46 # Populated with the settings dictionary from application init in
47 47 # rhodecode.conf.environment.load_pyramid_environment
48 48 PYRAMID_SETTINGS = {}
49 49
50 50 # Linked module for extensions
51 51 EXTENSIONS = {}
52 52
53 53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 78 # defines current db version for migrations
54 __dbversion__ = 79 # defines current db version for migrations
55 55 __platform__ = platform.system()
56 56 __license__ = 'AGPLv3, and Commercial License'
57 57 __author__ = 'RhodeCode GmbH'
58 58 __url__ = 'https://code.rhodecode.com'
59 59
60 60 is_windows = __platform__ in ['Windows']
61 61 is_unix = not is_windows
62 62 is_test = False
63 63 disable_error_handler = False
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,1028 +1,1029 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Repository model for rhodecode
23 23 """
24 24
25 25 import logging
26 26 import os
27 27 import re
28 28 import shutil
29 29 import time
30 30 import traceback
31 from datetime import datetime, timedelta
31 import datetime
32 32
33 33 from pyramid.threadlocal import get_current_request
34 34 from zope.cachedescriptors.property import Lazy as LazyProperty
35 35
36 36 from rhodecode import events
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import HasUserGroupPermissionAny
39 39 from rhodecode.lib.caching_query import FromCache
40 40 from rhodecode.lib.exceptions import AttachedForksError
41 41 from rhodecode.lib.hooks_base import log_delete_repository
42 42 from rhodecode.lib.utils import make_db_config
43 43 from rhodecode.lib.utils2 import (
44 44 safe_str, safe_unicode, remove_prefix, obfuscate_url_pw,
45 45 get_current_rhodecode_user, safe_int, datetime_to_time, action_logger_generic)
46 46 from rhodecode.lib.vcs.backends import get_backend
47 47 from rhodecode.model import BaseModel
48 48 from rhodecode.model.db import (_hash_key,
49 49 Repository, UserRepoToPerm, UserGroupRepoToPerm, UserRepoGroupToPerm,
50 50 UserGroupRepoGroupToPerm, User, Permission, Statistics, UserGroup,
51 51 RepoGroup, RepositoryField)
52 52
53 53 from rhodecode.model.settings import VcsSettingsModel
54 54
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 class RepoModel(BaseModel):
60 60
61 61 cls = Repository
62 62
63 63 def _get_user_group(self, users_group):
64 64 return self._get_instance(UserGroup, users_group,
65 65 callback=UserGroup.get_by_group_name)
66 66
67 67 def _get_repo_group(self, repo_group):
68 68 return self._get_instance(RepoGroup, repo_group,
69 69 callback=RepoGroup.get_by_group_name)
70 70
71 71 def _create_default_perms(self, repository, private):
72 72 # create default permission
73 73 default = 'repository.read'
74 74 def_user = User.get_default_user()
75 75 for p in def_user.user_perms:
76 76 if p.permission.permission_name.startswith('repository.'):
77 77 default = p.permission.permission_name
78 78 break
79 79
80 80 default_perm = 'repository.none' if private else default
81 81
82 82 repo_to_perm = UserRepoToPerm()
83 83 repo_to_perm.permission = Permission.get_by_key(default_perm)
84 84
85 85 repo_to_perm.repository = repository
86 86 repo_to_perm.user_id = def_user.user_id
87 87
88 88 return repo_to_perm
89 89
90 90 @LazyProperty
91 91 def repos_path(self):
92 92 """
93 93 Gets the repositories root path from database
94 94 """
95 95 settings_model = VcsSettingsModel(sa=self.sa)
96 96 return settings_model.get_repos_location()
97 97
98 98 def get(self, repo_id, cache=False):
99 99 repo = self.sa.query(Repository) \
100 100 .filter(Repository.repo_id == repo_id)
101 101
102 102 if cache:
103 103 repo = repo.options(
104 104 FromCache("sql_cache_short", "get_repo_%s" % repo_id))
105 105 return repo.scalar()
106 106
107 107 def get_repo(self, repository):
108 108 return self._get_repo(repository)
109 109
110 110 def get_by_repo_name(self, repo_name, cache=False):
111 111 repo = self.sa.query(Repository) \
112 112 .filter(Repository.repo_name == repo_name)
113 113
114 114 if cache:
115 115 name_key = _hash_key(repo_name)
116 116 repo = repo.options(
117 117 FromCache("sql_cache_short", "get_repo_%s" % name_key))
118 118 return repo.scalar()
119 119
120 120 def _extract_id_from_repo_name(self, repo_name):
121 121 if repo_name.startswith('/'):
122 122 repo_name = repo_name.lstrip('/')
123 123 by_id_match = re.match(r'^_(\d{1,})', repo_name)
124 124 if by_id_match:
125 125 return by_id_match.groups()[0]
126 126
127 127 def get_repo_by_id(self, repo_name):
128 128 """
129 129 Extracts repo_name by id from special urls.
130 130 Example url is _11/repo_name
131 131
132 132 :param repo_name:
133 133 :return: repo object if matched else None
134 134 """
135 135
136 136 try:
137 137 _repo_id = self._extract_id_from_repo_name(repo_name)
138 138 if _repo_id:
139 139 return self.get(_repo_id)
140 140 except Exception:
141 141 log.exception('Failed to extract repo_name from URL')
142 142
143 143 return None
144 144
145 145 def get_repos_for_root(self, root, traverse=False):
146 146 if traverse:
147 147 like_expression = u'{}%'.format(safe_unicode(root))
148 148 repos = Repository.query().filter(
149 149 Repository.repo_name.like(like_expression)).all()
150 150 else:
151 151 if root and not isinstance(root, RepoGroup):
152 152 raise ValueError(
153 153 'Root must be an instance '
154 154 'of RepoGroup, got:{} instead'.format(type(root)))
155 155 repos = Repository.query().filter(Repository.group == root).all()
156 156 return repos
157 157
158 158 def get_url(self, repo, request=None, permalink=False):
159 159 if not request:
160 160 request = get_current_request()
161 161
162 162 if not request:
163 163 return
164 164
165 165 if permalink:
166 166 return request.route_url(
167 167 'repo_summary', repo_name=safe_str(repo.repo_id))
168 168 else:
169 169 return request.route_url(
170 170 'repo_summary', repo_name=safe_str(repo.repo_name))
171 171
172 172 def get_commit_url(self, repo, commit_id, request=None, permalink=False):
173 173 if not request:
174 174 request = get_current_request()
175 175
176 176 if not request:
177 177 return
178 178
179 179 if permalink:
180 180 return request.route_url(
181 181 'repo_commit', repo_name=safe_str(repo.repo_id),
182 182 commit_id=commit_id)
183 183
184 184 else:
185 185 return request.route_url(
186 186 'repo_commit', repo_name=safe_str(repo.repo_name),
187 187 commit_id=commit_id)
188 188
189 189 @classmethod
190 190 def update_repoinfo(cls, repositories=None):
191 191 if not repositories:
192 192 repositories = Repository.getAll()
193 193 for repo in repositories:
194 194 repo.update_commit_cache()
195 195
196 196 def get_repos_as_dict(self, repo_list=None, admin=False,
197 197 super_user_actions=False):
198 198 _render = get_current_request().get_partial_renderer(
199 199 'data_table/_dt_elements.mako')
200 200 c = _render.get_call_context()
201 201
202 202 def quick_menu(repo_name):
203 203 return _render('quick_menu', repo_name)
204 204
205 205 def repo_lnk(name, rtype, rstate, private, fork_of):
206 206 return _render('repo_name', name, rtype, rstate, private, fork_of,
207 207 short_name=not admin, admin=False)
208 208
209 209 def last_change(last_change):
210 if admin and isinstance(last_change, datetime) and not last_change.tzinfo:
211 last_change = last_change + timedelta(seconds=
212 (datetime.now() - datetime.utcnow()).seconds)
210 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
211 last_change = last_change + datetime.timedelta(seconds=
212 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
213 213 return _render("last_change", last_change)
214 214
215 215 def rss_lnk(repo_name):
216 216 return _render("rss", repo_name)
217 217
218 218 def atom_lnk(repo_name):
219 219 return _render("atom", repo_name)
220 220
221 221 def last_rev(repo_name, cs_cache):
222 222 return _render('revision', repo_name, cs_cache.get('revision'),
223 223 cs_cache.get('raw_id'), cs_cache.get('author'),
224 224 cs_cache.get('message'))
225 225
226 226 def desc(desc):
227 227 if c.visual.stylify_metatags:
228 228 desc = h.urlify_text(h.escaped_stylize(desc))
229 229 else:
230 230 desc = h.urlify_text(h.html_escape(desc))
231 231
232 232 return _render('repo_desc', desc)
233 233
234 234 def state(repo_state):
235 235 return _render("repo_state", repo_state)
236 236
237 237 def repo_actions(repo_name):
238 238 return _render('repo_actions', repo_name, super_user_actions)
239 239
240 240 def user_profile(username):
241 241 return _render('user_profile', username)
242 242
243 243 repos_data = []
244 244 for repo in repo_list:
245 245 cs_cache = repo.changeset_cache
246 246 row = {
247 247 "menu": quick_menu(repo.repo_name),
248 248
249 249 "name": repo_lnk(repo.repo_name, repo.repo_type,
250 250 repo.repo_state, repo.private, repo.fork),
251 251 "name_raw": repo.repo_name.lower(),
252 252
253 253 "last_change": last_change(repo.last_db_change),
254 254 "last_change_raw": datetime_to_time(repo.last_db_change),
255 255
256 256 "last_changeset": last_rev(repo.repo_name, cs_cache),
257 257 "last_changeset_raw": cs_cache.get('revision'),
258 258
259 259 "desc": desc(repo.description_safe),
260 260 "owner": user_profile(repo.user.username),
261 261
262 262 "state": state(repo.repo_state),
263 263 "rss": rss_lnk(repo.repo_name),
264 264
265 265 "atom": atom_lnk(repo.repo_name),
266 266 }
267 267 if admin:
268 268 row.update({
269 269 "action": repo_actions(repo.repo_name),
270 270 })
271 271 repos_data.append(row)
272 272
273 273 return repos_data
274 274
275 275 def _get_defaults(self, repo_name):
276 276 """
277 277 Gets information about repository, and returns a dict for
278 278 usage in forms
279 279
280 280 :param repo_name:
281 281 """
282 282
283 283 repo_info = Repository.get_by_repo_name(repo_name)
284 284
285 285 if repo_info is None:
286 286 return None
287 287
288 288 defaults = repo_info.get_dict()
289 289 defaults['repo_name'] = repo_info.just_name
290 290
291 291 groups = repo_info.groups_with_parents
292 292 parent_group = groups[-1] if groups else None
293 293
294 294 # we use -1 as this is how in HTML, we mark an empty group
295 295 defaults['repo_group'] = getattr(parent_group, 'group_id', -1)
296 296
297 297 keys_to_process = (
298 298 {'k': 'repo_type', 'strip': False},
299 299 {'k': 'repo_enable_downloads', 'strip': True},
300 300 {'k': 'repo_description', 'strip': True},
301 301 {'k': 'repo_enable_locking', 'strip': True},
302 302 {'k': 'repo_landing_rev', 'strip': True},
303 303 {'k': 'clone_uri', 'strip': False},
304 304 {'k': 'repo_private', 'strip': True},
305 305 {'k': 'repo_enable_statistics', 'strip': True}
306 306 )
307 307
308 308 for item in keys_to_process:
309 309 attr = item['k']
310 310 if item['strip']:
311 311 attr = remove_prefix(item['k'], 'repo_')
312 312
313 313 val = defaults[attr]
314 314 if item['k'] == 'repo_landing_rev':
315 315 val = ':'.join(defaults[attr])
316 316 defaults[item['k']] = val
317 317 if item['k'] == 'clone_uri':
318 318 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
319 319
320 320 # fill owner
321 321 if repo_info.user:
322 322 defaults.update({'user': repo_info.user.username})
323 323 else:
324 324 replacement_user = User.get_first_super_admin().username
325 325 defaults.update({'user': replacement_user})
326 326
327 327 return defaults
328 328
329 329 def update(self, repo, **kwargs):
330 330 try:
331 331 cur_repo = self._get_repo(repo)
332 332 source_repo_name = cur_repo.repo_name
333 333 if 'user' in kwargs:
334 334 cur_repo.user = User.get_by_username(kwargs['user'])
335 335
336 336 if 'repo_group' in kwargs:
337 337 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
338 338 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
339 339
340 340 update_keys = [
341 341 (1, 'repo_description'),
342 342 (1, 'repo_landing_rev'),
343 343 (1, 'repo_private'),
344 344 (1, 'repo_enable_downloads'),
345 345 (1, 'repo_enable_locking'),
346 346 (1, 'repo_enable_statistics'),
347 347 (0, 'clone_uri'),
348 348 (0, 'fork_id')
349 349 ]
350 350 for strip, k in update_keys:
351 351 if k in kwargs:
352 352 val = kwargs[k]
353 353 if strip:
354 354 k = remove_prefix(k, 'repo_')
355 355
356 356 setattr(cur_repo, k, val)
357 357
358 358 new_name = cur_repo.get_new_name(kwargs['repo_name'])
359 359 cur_repo.repo_name = new_name
360 360
361 361 # if private flag is set, reset default permission to NONE
362 362 if kwargs.get('repo_private'):
363 363 EMPTY_PERM = 'repository.none'
364 364 RepoModel().grant_user_permission(
365 365 repo=cur_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM
366 366 )
367 367
368 368 # handle extra fields
369 369 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
370 370 kwargs):
371 371 k = RepositoryField.un_prefix_key(field)
372 372 ex_field = RepositoryField.get_by_key_name(
373 373 key=k, repo=cur_repo)
374 374 if ex_field:
375 375 ex_field.field_value = kwargs[field]
376 376 self.sa.add(ex_field)
377 cur_repo.updated_on = datetime.datetime.now()
377 378 self.sa.add(cur_repo)
378 379
379 380 if source_repo_name != new_name:
380 381 # rename repository
381 382 self._rename_filesystem_repo(
382 383 old=source_repo_name, new=new_name)
383 384
384 385 return cur_repo
385 386 except Exception:
386 387 log.error(traceback.format_exc())
387 388 raise
388 389
389 390 def _create_repo(self, repo_name, repo_type, description, owner,
390 391 private=False, clone_uri=None, repo_group=None,
391 392 landing_rev='rev:tip', fork_of=None,
392 393 copy_fork_permissions=False, enable_statistics=False,
393 394 enable_locking=False, enable_downloads=False,
394 395 copy_group_permissions=False,
395 396 state=Repository.STATE_PENDING):
396 397 """
397 398 Create repository inside database with PENDING state, this should be
398 399 only executed by create() repo. With exception of importing existing
399 400 repos
400 401 """
401 402 from rhodecode.model.scm import ScmModel
402 403
403 404 owner = self._get_user(owner)
404 405 fork_of = self._get_repo(fork_of)
405 406 repo_group = self._get_repo_group(safe_int(repo_group))
406 407
407 408 try:
408 409 repo_name = safe_unicode(repo_name)
409 410 description = safe_unicode(description)
410 411 # repo name is just a name of repository
411 412 # while repo_name_full is a full qualified name that is combined
412 413 # with name and path of group
413 414 repo_name_full = repo_name
414 415 repo_name = repo_name.split(Repository.NAME_SEP)[-1]
415 416
416 417 new_repo = Repository()
417 418 new_repo.repo_state = state
418 419 new_repo.enable_statistics = False
419 420 new_repo.repo_name = repo_name_full
420 421 new_repo.repo_type = repo_type
421 422 new_repo.user = owner
422 423 new_repo.group = repo_group
423 424 new_repo.description = description or repo_name
424 425 new_repo.private = private
425 426 new_repo.clone_uri = clone_uri
426 427 new_repo.landing_rev = landing_rev
427 428
428 429 new_repo.enable_statistics = enable_statistics
429 430 new_repo.enable_locking = enable_locking
430 431 new_repo.enable_downloads = enable_downloads
431 432
432 433 if repo_group:
433 434 new_repo.enable_locking = repo_group.enable_locking
434 435
435 436 if fork_of:
436 437 parent_repo = fork_of
437 438 new_repo.fork = parent_repo
438 439
439 440 events.trigger(events.RepoPreCreateEvent(new_repo))
440 441
441 442 self.sa.add(new_repo)
442 443
443 444 EMPTY_PERM = 'repository.none'
444 445 if fork_of and copy_fork_permissions:
445 446 repo = fork_of
446 447 user_perms = UserRepoToPerm.query() \
447 448 .filter(UserRepoToPerm.repository == repo).all()
448 449 group_perms = UserGroupRepoToPerm.query() \
449 450 .filter(UserGroupRepoToPerm.repository == repo).all()
450 451
451 452 for perm in user_perms:
452 453 UserRepoToPerm.create(
453 454 perm.user, new_repo, perm.permission)
454 455
455 456 for perm in group_perms:
456 457 UserGroupRepoToPerm.create(
457 458 perm.users_group, new_repo, perm.permission)
458 459 # in case we copy permissions and also set this repo to private
459 460 # override the default user permission to make it a private
460 461 # repo
461 462 if private:
462 463 RepoModel(self.sa).grant_user_permission(
463 464 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
464 465
465 466 elif repo_group and copy_group_permissions:
466 467 user_perms = UserRepoGroupToPerm.query() \
467 468 .filter(UserRepoGroupToPerm.group == repo_group).all()
468 469
469 470 group_perms = UserGroupRepoGroupToPerm.query() \
470 471 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
471 472
472 473 for perm in user_perms:
473 474 perm_name = perm.permission.permission_name.replace(
474 475 'group.', 'repository.')
475 476 perm_obj = Permission.get_by_key(perm_name)
476 477 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
477 478
478 479 for perm in group_perms:
479 480 perm_name = perm.permission.permission_name.replace(
480 481 'group.', 'repository.')
481 482 perm_obj = Permission.get_by_key(perm_name)
482 483 UserGroupRepoToPerm.create(
483 484 perm.users_group, new_repo, perm_obj)
484 485
485 486 if private:
486 487 RepoModel(self.sa).grant_user_permission(
487 488 repo=new_repo, user=User.DEFAULT_USER, perm=EMPTY_PERM)
488 489
489 490 else:
490 491 perm_obj = self._create_default_perms(new_repo, private)
491 492 self.sa.add(perm_obj)
492 493
493 494 # now automatically start following this repository as owner
494 495 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
495 496 owner.user_id)
496 497
497 498 # we need to flush here, in order to check if database won't
498 499 # throw any exceptions, create filesystem dirs at the very end
499 500 self.sa.flush()
500 501 events.trigger(events.RepoCreateEvent(new_repo))
501 502 return new_repo
502 503
503 504 except Exception:
504 505 log.error(traceback.format_exc())
505 506 raise
506 507
507 508 def create(self, form_data, cur_user):
508 509 """
509 510 Create repository using celery tasks
510 511
511 512 :param form_data:
512 513 :param cur_user:
513 514 """
514 515 from rhodecode.lib.celerylib import tasks, run_task
515 516 return run_task(tasks.create_repo, form_data, cur_user)
516 517
517 518 def update_permissions(self, repo, perm_additions=None, perm_updates=None,
518 519 perm_deletions=None, check_perms=True,
519 520 cur_user=None):
520 521 if not perm_additions:
521 522 perm_additions = []
522 523 if not perm_updates:
523 524 perm_updates = []
524 525 if not perm_deletions:
525 526 perm_deletions = []
526 527
527 528 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
528 529
529 530 changes = {
530 531 'added': [],
531 532 'updated': [],
532 533 'deleted': []
533 534 }
534 535 # update permissions
535 536 for member_id, perm, member_type in perm_updates:
536 537 member_id = int(member_id)
537 538 if member_type == 'user':
538 539 member_name = User.get(member_id).username
539 540 # this updates also current one if found
540 541 self.grant_user_permission(
541 542 repo=repo, user=member_id, perm=perm)
542 543 else: # set for user group
543 544 # check if we have permissions to alter this usergroup
544 545 member_name = UserGroup.get(member_id).users_group_name
545 546 if not check_perms or HasUserGroupPermissionAny(
546 547 *req_perms)(member_name, user=cur_user):
547 548 self.grant_user_group_permission(
548 549 repo=repo, group_name=member_id, perm=perm)
549 550
550 551 changes['updated'].append({'type': member_type, 'id': member_id,
551 552 'name': member_name, 'new_perm': perm})
552 553
553 554 # set new permissions
554 555 for member_id, perm, member_type in perm_additions:
555 556 member_id = int(member_id)
556 557 if member_type == 'user':
557 558 member_name = User.get(member_id).username
558 559 self.grant_user_permission(
559 560 repo=repo, user=member_id, perm=perm)
560 561 else: # set for user group
561 562 # check if we have permissions to alter this usergroup
562 563 member_name = UserGroup.get(member_id).users_group_name
563 564 if not check_perms or HasUserGroupPermissionAny(
564 565 *req_perms)(member_name, user=cur_user):
565 566 self.grant_user_group_permission(
566 567 repo=repo, group_name=member_id, perm=perm)
567 568 changes['added'].append({'type': member_type, 'id': member_id,
568 569 'name': member_name, 'new_perm': perm})
569 570 # delete permissions
570 571 for member_id, perm, member_type in perm_deletions:
571 572 member_id = int(member_id)
572 573 if member_type == 'user':
573 574 member_name = User.get(member_id).username
574 575 self.revoke_user_permission(repo=repo, user=member_id)
575 576 else: # set for user group
576 577 # check if we have permissions to alter this usergroup
577 578 member_name = UserGroup.get(member_id).users_group_name
578 579 if not check_perms or HasUserGroupPermissionAny(
579 580 *req_perms)(member_name, user=cur_user):
580 581 self.revoke_user_group_permission(
581 582 repo=repo, group_name=member_id)
582 583
583 584 changes['deleted'].append({'type': member_type, 'id': member_id,
584 585 'name': member_name, 'new_perm': perm})
585 586 return changes
586 587
587 588 def create_fork(self, form_data, cur_user):
588 589 """
589 590 Simple wrapper into executing celery task for fork creation
590 591
591 592 :param form_data:
592 593 :param cur_user:
593 594 """
594 595 from rhodecode.lib.celerylib import tasks, run_task
595 596 return run_task(tasks.create_repo_fork, form_data, cur_user)
596 597
597 598 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
598 599 """
599 600 Delete given repository, forks parameter defines what do do with
600 601 attached forks. Throws AttachedForksError if deleted repo has attached
601 602 forks
602 603
603 604 :param repo:
604 605 :param forks: str 'delete' or 'detach'
605 606 :param fs_remove: remove(archive) repo from filesystem
606 607 """
607 608 if not cur_user:
608 609 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
609 610 repo = self._get_repo(repo)
610 611 if repo:
611 612 if forks == 'detach':
612 613 for r in repo.forks:
613 614 r.fork = None
614 615 self.sa.add(r)
615 616 elif forks == 'delete':
616 617 for r in repo.forks:
617 618 self.delete(r, forks='delete')
618 619 elif [f for f in repo.forks]:
619 620 raise AttachedForksError()
620 621
621 622 old_repo_dict = repo.get_dict()
622 623 events.trigger(events.RepoPreDeleteEvent(repo))
623 624 try:
624 625 self.sa.delete(repo)
625 626 if fs_remove:
626 627 self._delete_filesystem_repo(repo)
627 628 else:
628 629 log.debug('skipping removal from filesystem')
629 630 old_repo_dict.update({
630 631 'deleted_by': cur_user,
631 632 'deleted_on': time.time(),
632 633 })
633 634 log_delete_repository(**old_repo_dict)
634 635 events.trigger(events.RepoDeleteEvent(repo))
635 636 except Exception:
636 637 log.error(traceback.format_exc())
637 638 raise
638 639
639 640 def grant_user_permission(self, repo, user, perm):
640 641 """
641 642 Grant permission for user on given repository, or update existing one
642 643 if found
643 644
644 645 :param repo: Instance of Repository, repository_id, or repository name
645 646 :param user: Instance of User, user_id or username
646 647 :param perm: Instance of Permission, or permission_name
647 648 """
648 649 user = self._get_user(user)
649 650 repo = self._get_repo(repo)
650 651 permission = self._get_perm(perm)
651 652
652 653 # check if we have that permission already
653 654 obj = self.sa.query(UserRepoToPerm) \
654 655 .filter(UserRepoToPerm.user == user) \
655 656 .filter(UserRepoToPerm.repository == repo) \
656 657 .scalar()
657 658 if obj is None:
658 659 # create new !
659 660 obj = UserRepoToPerm()
660 661 obj.repository = repo
661 662 obj.user = user
662 663 obj.permission = permission
663 664 self.sa.add(obj)
664 665 log.debug('Granted perm %s to %s on %s', perm, user, repo)
665 666 action_logger_generic(
666 667 'granted permission: {} to user: {} on repo: {}'.format(
667 668 perm, user, repo), namespace='security.repo')
668 669 return obj
669 670
670 671 def revoke_user_permission(self, repo, user):
671 672 """
672 673 Revoke permission for user on given repository
673 674
674 675 :param repo: Instance of Repository, repository_id, or repository name
675 676 :param user: Instance of User, user_id or username
676 677 """
677 678
678 679 user = self._get_user(user)
679 680 repo = self._get_repo(repo)
680 681
681 682 obj = self.sa.query(UserRepoToPerm) \
682 683 .filter(UserRepoToPerm.repository == repo) \
683 684 .filter(UserRepoToPerm.user == user) \
684 685 .scalar()
685 686 if obj:
686 687 self.sa.delete(obj)
687 688 log.debug('Revoked perm on %s on %s', repo, user)
688 689 action_logger_generic(
689 690 'revoked permission from user: {} on repo: {}'.format(
690 691 user, repo), namespace='security.repo')
691 692
692 693 def grant_user_group_permission(self, repo, group_name, perm):
693 694 """
694 695 Grant permission for user group on given repository, or update
695 696 existing one if found
696 697
697 698 :param repo: Instance of Repository, repository_id, or repository name
698 699 :param group_name: Instance of UserGroup, users_group_id,
699 700 or user group name
700 701 :param perm: Instance of Permission, or permission_name
701 702 """
702 703 repo = self._get_repo(repo)
703 704 group_name = self._get_user_group(group_name)
704 705 permission = self._get_perm(perm)
705 706
706 707 # check if we have that permission already
707 708 obj = self.sa.query(UserGroupRepoToPerm) \
708 709 .filter(UserGroupRepoToPerm.users_group == group_name) \
709 710 .filter(UserGroupRepoToPerm.repository == repo) \
710 711 .scalar()
711 712
712 713 if obj is None:
713 714 # create new
714 715 obj = UserGroupRepoToPerm()
715 716
716 717 obj.repository = repo
717 718 obj.users_group = group_name
718 719 obj.permission = permission
719 720 self.sa.add(obj)
720 721 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
721 722 action_logger_generic(
722 723 'granted permission: {} to usergroup: {} on repo: {}'.format(
723 724 perm, group_name, repo), namespace='security.repo')
724 725
725 726 return obj
726 727
727 728 def revoke_user_group_permission(self, repo, group_name):
728 729 """
729 730 Revoke permission for user group on given repository
730 731
731 732 :param repo: Instance of Repository, repository_id, or repository name
732 733 :param group_name: Instance of UserGroup, users_group_id,
733 734 or user group name
734 735 """
735 736 repo = self._get_repo(repo)
736 737 group_name = self._get_user_group(group_name)
737 738
738 739 obj = self.sa.query(UserGroupRepoToPerm) \
739 740 .filter(UserGroupRepoToPerm.repository == repo) \
740 741 .filter(UserGroupRepoToPerm.users_group == group_name) \
741 742 .scalar()
742 743 if obj:
743 744 self.sa.delete(obj)
744 745 log.debug('Revoked perm to %s on %s', repo, group_name)
745 746 action_logger_generic(
746 747 'revoked permission from usergroup: {} on repo: {}'.format(
747 748 group_name, repo), namespace='security.repo')
748 749
749 750 def delete_stats(self, repo_name):
750 751 """
751 752 removes stats for given repo
752 753
753 754 :param repo_name:
754 755 """
755 756 repo = self._get_repo(repo_name)
756 757 try:
757 758 obj = self.sa.query(Statistics) \
758 759 .filter(Statistics.repository == repo).scalar()
759 760 if obj:
760 761 self.sa.delete(obj)
761 762 except Exception:
762 763 log.error(traceback.format_exc())
763 764 raise
764 765
765 766 def add_repo_field(self, repo_name, field_key, field_label, field_value='',
766 767 field_type='str', field_desc=''):
767 768
768 769 repo = self._get_repo(repo_name)
769 770
770 771 new_field = RepositoryField()
771 772 new_field.repository = repo
772 773 new_field.field_key = field_key
773 774 new_field.field_type = field_type # python type
774 775 new_field.field_value = field_value
775 776 new_field.field_desc = field_desc
776 777 new_field.field_label = field_label
777 778 self.sa.add(new_field)
778 779 return new_field
779 780
780 781 def delete_repo_field(self, repo_name, field_key):
781 782 repo = self._get_repo(repo_name)
782 783 field = RepositoryField.get_by_key_name(field_key, repo)
783 784 if field:
784 785 self.sa.delete(field)
785 786
786 787 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
787 788 clone_uri=None, repo_store_location=None,
788 789 use_global_config=False):
789 790 """
790 791 makes repository on filesystem. It's group aware means it'll create
791 792 a repository within a group, and alter the paths accordingly of
792 793 group location
793 794
794 795 :param repo_name:
795 796 :param alias:
796 797 :param parent:
797 798 :param clone_uri:
798 799 :param repo_store_location:
799 800 """
800 801 from rhodecode.lib.utils import is_valid_repo, is_valid_repo_group
801 802 from rhodecode.model.scm import ScmModel
802 803
803 804 if Repository.NAME_SEP in repo_name:
804 805 raise ValueError(
805 806 'repo_name must not contain groups got `%s`' % repo_name)
806 807
807 808 if isinstance(repo_group, RepoGroup):
808 809 new_parent_path = os.sep.join(repo_group.full_path_splitted)
809 810 else:
810 811 new_parent_path = repo_group or ''
811 812
812 813 if repo_store_location:
813 814 _paths = [repo_store_location]
814 815 else:
815 816 _paths = [self.repos_path, new_parent_path, repo_name]
816 817 # we need to make it str for mercurial
817 818 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
818 819
819 820 # check if this path is not a repository
820 821 if is_valid_repo(repo_path, self.repos_path):
821 822 raise Exception('This path %s is a valid repository' % repo_path)
822 823
823 824 # check if this path is a group
824 825 if is_valid_repo_group(repo_path, self.repos_path):
825 826 raise Exception('This path %s is a valid group' % repo_path)
826 827
827 828 log.info('creating repo %s in %s from url: `%s`',
828 829 repo_name, safe_unicode(repo_path),
829 830 obfuscate_url_pw(clone_uri))
830 831
831 832 backend = get_backend(repo_type)
832 833
833 834 config_repo = None if use_global_config else repo_name
834 835 if config_repo and new_parent_path:
835 836 config_repo = Repository.NAME_SEP.join(
836 837 (new_parent_path, config_repo))
837 838 config = make_db_config(clear_session=False, repo=config_repo)
838 839 config.set('extensions', 'largefiles', '')
839 840
840 841 # patch and reset hooks section of UI config to not run any
841 842 # hooks on creating remote repo
842 843 config.clear_section('hooks')
843 844
844 845 # TODO: johbo: Unify this, hardcoded "bare=True" does not look nice
845 846 if repo_type == 'git':
846 847 repo = backend(
847 848 repo_path, config=config, create=True, src_url=clone_uri,
848 849 bare=True)
849 850 else:
850 851 repo = backend(
851 852 repo_path, config=config, create=True, src_url=clone_uri)
852 853
853 854 ScmModel().install_hooks(repo, repo_type=repo_type)
854 855
855 856 log.debug('Created repo %s with %s backend',
856 857 safe_unicode(repo_name), safe_unicode(repo_type))
857 858 return repo
858 859
859 860 def _rename_filesystem_repo(self, old, new):
860 861 """
861 862 renames repository on filesystem
862 863
863 864 :param old: old name
864 865 :param new: new name
865 866 """
866 867 log.info('renaming repo from %s to %s', old, new)
867 868
868 869 old_path = os.path.join(self.repos_path, old)
869 870 new_path = os.path.join(self.repos_path, new)
870 871 if os.path.isdir(new_path):
871 872 raise Exception(
872 873 'Was trying to rename to already existing dir %s' % new_path
873 874 )
874 875 shutil.move(old_path, new_path)
875 876
876 877 def _delete_filesystem_repo(self, repo):
877 878 """
878 879 removes repo from filesystem, the removal is acctually made by
879 880 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
880 881 repository is no longer valid for rhodecode, can be undeleted later on
881 882 by reverting the renames on this repository
882 883
883 884 :param repo: repo object
884 885 """
885 886 rm_path = os.path.join(self.repos_path, repo.repo_name)
886 887 repo_group = repo.group
887 888 log.info("Removing repository %s", rm_path)
888 889 # disable hg/git internal that it doesn't get detected as repo
889 890 alias = repo.repo_type
890 891
891 892 config = make_db_config(clear_session=False)
892 893 config.set('extensions', 'largefiles', '')
893 894 bare = getattr(repo.scm_instance(config=config), 'bare', False)
894 895
895 896 # skip this for bare git repos
896 897 if not bare:
897 898 # disable VCS repo
898 899 vcs_path = os.path.join(rm_path, '.%s' % alias)
899 900 if os.path.exists(vcs_path):
900 901 shutil.move(vcs_path, os.path.join(rm_path, 'rm__.%s' % alias))
901 902
902 _now = datetime.now()
903 _now = datetime.datetime.now()
903 904 _ms = str(_now.microsecond).rjust(6, '0')
904 905 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
905 906 repo.just_name)
906 907 if repo_group:
907 908 # if repository is in group, prefix the removal path with the group
908 909 args = repo_group.full_path_splitted + [_d]
909 910 _d = os.path.join(*args)
910 911
911 912 if os.path.isdir(rm_path):
912 913 shutil.move(rm_path, os.path.join(self.repos_path, _d))
913 914
914 915
915 916 class ReadmeFinder:
916 917 """
917 918 Utility which knows how to find a readme for a specific commit.
918 919
919 920 The main idea is that this is a configurable algorithm. When creating an
920 921 instance you can define parameters, currently only the `default_renderer`.
921 922 Based on this configuration the method :meth:`search` behaves slightly
922 923 different.
923 924 """
924 925
925 926 readme_re = re.compile(r'^readme(\.[^\.]+)?$', re.IGNORECASE)
926 927 path_re = re.compile(r'^docs?', re.IGNORECASE)
927 928
928 929 default_priorities = {
929 930 None: 0,
930 931 '.text': 2,
931 932 '.txt': 3,
932 933 '.rst': 1,
933 934 '.rest': 2,
934 935 '.md': 1,
935 936 '.mkdn': 2,
936 937 '.mdown': 3,
937 938 '.markdown': 4,
938 939 }
939 940
940 941 path_priority = {
941 942 'doc': 0,
942 943 'docs': 1,
943 944 }
944 945
945 946 FALLBACK_PRIORITY = 99
946 947
947 948 RENDERER_TO_EXTENSION = {
948 949 'rst': ['.rst', '.rest'],
949 950 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'],
950 951 }
951 952
952 953 def __init__(self, default_renderer=None):
953 954 self._default_renderer = default_renderer
954 955 self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(
955 956 default_renderer, [])
956 957
957 958 def search(self, commit, path='/'):
958 959 """
959 960 Find a readme in the given `commit`.
960 961 """
961 962 nodes = commit.get_nodes(path)
962 963 matches = self._match_readmes(nodes)
963 964 matches = self._sort_according_to_priority(matches)
964 965 if matches:
965 966 return matches[0].node
966 967
967 968 paths = self._match_paths(nodes)
968 969 paths = self._sort_paths_according_to_priority(paths)
969 970 for path in paths:
970 971 match = self.search(commit, path=path)
971 972 if match:
972 973 return match
973 974
974 975 return None
975 976
976 977 def _match_readmes(self, nodes):
977 978 for node in nodes:
978 979 if not node.is_file():
979 980 continue
980 981 path = node.path.rsplit('/', 1)[-1]
981 982 match = self.readme_re.match(path)
982 983 if match:
983 984 extension = match.group(1)
984 985 yield ReadmeMatch(node, match, self._priority(extension))
985 986
986 987 def _match_paths(self, nodes):
987 988 for node in nodes:
988 989 if not node.is_dir():
989 990 continue
990 991 match = self.path_re.match(node.path)
991 992 if match:
992 993 yield node.path
993 994
994 995 def _priority(self, extension):
995 996 renderer_priority = (
996 997 0 if extension in self._renderer_extensions else 1)
997 998 extension_priority = self.default_priorities.get(
998 999 extension, self.FALLBACK_PRIORITY)
999 1000 return (renderer_priority, extension_priority)
1000 1001
1001 1002 def _sort_according_to_priority(self, matches):
1002 1003
1003 1004 def priority_and_path(match):
1004 1005 return (match.priority, match.path)
1005 1006
1006 1007 return sorted(matches, key=priority_and_path)
1007 1008
1008 1009 def _sort_paths_according_to_priority(self, paths):
1009 1010
1010 1011 def priority_and_path(path):
1011 1012 return (self.path_priority.get(path, self.FALLBACK_PRIORITY), path)
1012 1013
1013 1014 return sorted(paths, key=priority_and_path)
1014 1015
1015 1016
1016 1017 class ReadmeMatch:
1017 1018
1018 1019 def __init__(self, node, match, priority):
1019 1020 self.node = node
1020 1021 self._match = match
1021 1022 self.priority = priority
1022 1023
1023 1024 @property
1024 1025 def path(self):
1025 1026 return self.node.path
1026 1027
1027 1028 def __repr__(self):
1028 1029 return '<ReadmeMatch {} priority={}'.format(self.path, self.priority)
@@ -1,734 +1,744 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 repo group model for RhodeCode
24 24 """
25 25
26 26 import os
27 27 import datetime
28 28 import itertools
29 29 import logging
30 30 import shutil
31 31 import traceback
32 32 import string
33 33
34 34 from zope.cachedescriptors.property import Lazy as LazyProperty
35 35
36 36 from rhodecode import events
37 37 from rhodecode.model import BaseModel
38 38 from rhodecode.model.db import (_hash_key,
39 39 RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 40 UserGroup, Repository)
41 41 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
42 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, datetime_to_time
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class RepoGroupModel(BaseModel):
49 49
50 50 cls = RepoGroup
51 51 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 52 PERSONAL_GROUP_PATTERN = '${username}' # default
53 53
54 54 def _get_user_group(self, users_group):
55 55 return self._get_instance(UserGroup, users_group,
56 56 callback=UserGroup.get_by_group_name)
57 57
58 58 def _get_repo_group(self, repo_group):
59 59 return self._get_instance(RepoGroup, repo_group,
60 60 callback=RepoGroup.get_by_group_name)
61 61
62 62 @LazyProperty
63 63 def repos_path(self):
64 64 """
65 65 Gets the repositories root path from database
66 66 """
67 67
68 68 settings_model = VcsSettingsModel(sa=self.sa)
69 69 return settings_model.get_repos_location()
70 70
71 71 def get_by_group_name(self, repo_group_name, cache=None):
72 72 repo = self.sa.query(RepoGroup) \
73 73 .filter(RepoGroup.group_name == repo_group_name)
74 74
75 75 if cache:
76 76 name_key = _hash_key(repo_group_name)
77 77 repo = repo.options(
78 78 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
79 79 return repo.scalar()
80 80
81 81 def get_default_create_personal_repo_group(self):
82 82 value = SettingsModel().get_setting_by_name(
83 83 'create_personal_repo_group')
84 84 return value.app_settings_value if value else None or False
85 85
86 86 def get_personal_group_name_pattern(self):
87 87 value = SettingsModel().get_setting_by_name(
88 88 'personal_repo_group_pattern')
89 89 val = value.app_settings_value if value else None
90 90 group_template = val or self.PERSONAL_GROUP_PATTERN
91 91
92 92 group_template = group_template.lstrip('/')
93 93 return group_template
94 94
95 95 def get_personal_group_name(self, user):
96 96 template = self.get_personal_group_name_pattern()
97 97 return string.Template(template).safe_substitute(
98 98 username=user.username,
99 99 user_id=user.user_id,
100 100 )
101 101
102 102 def create_personal_repo_group(self, user, commit_early=True):
103 103 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
104 104 personal_repo_group_name = self.get_personal_group_name(user)
105 105
106 106 # create a new one
107 107 RepoGroupModel().create(
108 108 group_name=personal_repo_group_name,
109 109 group_description=desc,
110 110 owner=user.username,
111 111 personal=True,
112 112 commit_early=commit_early)
113 113
114 114 def _create_default_perms(self, new_group):
115 115 # create default permission
116 116 default_perm = 'group.read'
117 117 def_user = User.get_default_user()
118 118 for p in def_user.user_perms:
119 119 if p.permission.permission_name.startswith('group.'):
120 120 default_perm = p.permission.permission_name
121 121 break
122 122
123 123 repo_group_to_perm = UserRepoGroupToPerm()
124 124 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
125 125
126 126 repo_group_to_perm.group = new_group
127 127 repo_group_to_perm.user_id = def_user.user_id
128 128 return repo_group_to_perm
129 129
130 130 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
131 131 get_object=False):
132 132 """
133 133 Get's the group name and a parent group name from given group name.
134 134 If repo_in_path is set to truth, we asume the full path also includes
135 135 repo name, in such case we clean the last element.
136 136
137 137 :param group_name_full:
138 138 """
139 139 split_paths = 1
140 140 if repo_in_path:
141 141 split_paths = 2
142 142 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
143 143
144 144 if repo_in_path and len(_parts) > 1:
145 145 # such case last element is the repo_name
146 146 _parts.pop(-1)
147 147 group_name_cleaned = _parts[-1] # just the group name
148 148 parent_repo_group_name = None
149 149
150 150 if len(_parts) > 1:
151 151 parent_repo_group_name = _parts[0]
152 152
153 153 parent_group = None
154 154 if parent_repo_group_name:
155 155 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
156 156
157 157 if get_object:
158 158 return group_name_cleaned, parent_repo_group_name, parent_group
159 159
160 160 return group_name_cleaned, parent_repo_group_name
161 161
162 162 def check_exist_filesystem(self, group_name, exc_on_failure=True):
163 163 create_path = os.path.join(self.repos_path, group_name)
164 164 log.debug('creating new group in %s', create_path)
165 165
166 166 if os.path.isdir(create_path):
167 167 if exc_on_failure:
168 168 abs_create_path = os.path.abspath(create_path)
169 169 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
170 170 return False
171 171 return True
172 172
173 173 def _create_group(self, group_name):
174 174 """
175 175 makes repository group on filesystem
176 176
177 177 :param repo_name:
178 178 :param parent_id:
179 179 """
180 180
181 181 self.check_exist_filesystem(group_name)
182 182 create_path = os.path.join(self.repos_path, group_name)
183 183 log.debug('creating new group in %s', create_path)
184 184 os.makedirs(create_path, mode=0755)
185 185 log.debug('created group in %s', create_path)
186 186
187 187 def _rename_group(self, old, new):
188 188 """
189 189 Renames a group on filesystem
190 190
191 191 :param group_name:
192 192 """
193 193
194 194 if old == new:
195 195 log.debug('skipping group rename')
196 196 return
197 197
198 198 log.debug('renaming repository group from %s to %s', old, new)
199 199
200 200 old_path = os.path.join(self.repos_path, old)
201 201 new_path = os.path.join(self.repos_path, new)
202 202
203 203 log.debug('renaming repos paths from %s to %s', old_path, new_path)
204 204
205 205 if os.path.isdir(new_path):
206 206 raise Exception('Was trying to rename to already '
207 207 'existing dir %s' % new_path)
208 208 shutil.move(old_path, new_path)
209 209
210 210 def _delete_filesystem_group(self, group, force_delete=False):
211 211 """
212 212 Deletes a group from a filesystem
213 213
214 214 :param group: instance of group from database
215 215 :param force_delete: use shutil rmtree to remove all objects
216 216 """
217 217 paths = group.full_path.split(RepoGroup.url_sep())
218 218 paths = os.sep.join(paths)
219 219
220 220 rm_path = os.path.join(self.repos_path, paths)
221 221 log.info("Removing group %s", rm_path)
222 222 # delete only if that path really exists
223 223 if os.path.isdir(rm_path):
224 224 if force_delete:
225 225 shutil.rmtree(rm_path)
226 226 else:
227 227 # archive that group`
228 228 _now = datetime.datetime.now()
229 229 _ms = str(_now.microsecond).rjust(6, '0')
230 230 _d = 'rm__%s_GROUP_%s' % (
231 231 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
232 232 shutil.move(rm_path, os.path.join(self.repos_path, _d))
233 233
234 234 def create(self, group_name, group_description, owner, just_db=False,
235 235 copy_permissions=False, personal=None, commit_early=True):
236 236
237 237 (group_name_cleaned,
238 238 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
239 239
240 240 parent_group = None
241 241 if parent_group_name:
242 242 parent_group = self._get_repo_group(parent_group_name)
243 243 if not parent_group:
244 244 # we tried to create a nested group, but the parent is not
245 245 # existing
246 246 raise ValueError(
247 247 'Parent group `%s` given in `%s` group name '
248 248 'is not yet existing.' % (parent_group_name, group_name))
249 249
250 250 # because we are doing a cleanup, we need to check if such directory
251 251 # already exists. If we don't do that we can accidentally delete
252 252 # existing directory via cleanup that can cause data issues, since
253 253 # delete does a folder rename to special syntax later cleanup
254 254 # functions can delete this
255 255 cleanup_group = self.check_exist_filesystem(group_name,
256 256 exc_on_failure=False)
257 257 try:
258 258 user = self._get_user(owner)
259 259 new_repo_group = RepoGroup()
260 260 new_repo_group.user = user
261 261 new_repo_group.group_description = group_description or group_name
262 262 new_repo_group.parent_group = parent_group
263 263 new_repo_group.group_name = group_name
264 264 new_repo_group.personal = personal
265 265
266 266 self.sa.add(new_repo_group)
267 267
268 268 # create an ADMIN permission for owner except if we're super admin,
269 269 # later owner should go into the owner field of groups
270 270 if not user.is_admin:
271 271 self.grant_user_permission(repo_group=new_repo_group,
272 272 user=owner, perm='group.admin')
273 273
274 274 if parent_group and copy_permissions:
275 275 # copy permissions from parent
276 276 user_perms = UserRepoGroupToPerm.query() \
277 277 .filter(UserRepoGroupToPerm.group == parent_group).all()
278 278
279 279 group_perms = UserGroupRepoGroupToPerm.query() \
280 280 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
281 281
282 282 for perm in user_perms:
283 283 # don't copy over the permission for user who is creating
284 284 # this group, if he is not super admin he get's admin
285 285 # permission set above
286 286 if perm.user != user or user.is_admin:
287 287 UserRepoGroupToPerm.create(
288 288 perm.user, new_repo_group, perm.permission)
289 289
290 290 for perm in group_perms:
291 291 UserGroupRepoGroupToPerm.create(
292 292 perm.users_group, new_repo_group, perm.permission)
293 293 else:
294 294 perm_obj = self._create_default_perms(new_repo_group)
295 295 self.sa.add(perm_obj)
296 296
297 297 # now commit the changes, earlier so we are sure everything is in
298 298 # the database.
299 299 if commit_early:
300 300 self.sa.commit()
301 301 if not just_db:
302 302 self._create_group(new_repo_group.group_name)
303 303
304 304 # trigger the post hook
305 305 from rhodecode.lib.hooks_base import log_create_repository_group
306 306 repo_group = RepoGroup.get_by_group_name(group_name)
307 307 log_create_repository_group(
308 308 created_by=user.username, **repo_group.get_dict())
309 309
310 310 # Trigger create event.
311 311 events.trigger(events.RepoGroupCreateEvent(repo_group))
312 312
313 313 return new_repo_group
314 314 except Exception:
315 315 self.sa.rollback()
316 316 log.exception('Exception occurred when creating repository group, '
317 317 'doing cleanup...')
318 318 # rollback things manually !
319 319 repo_group = RepoGroup.get_by_group_name(group_name)
320 320 if repo_group:
321 321 RepoGroup.delete(repo_group.group_id)
322 322 self.sa.commit()
323 323 if cleanup_group:
324 324 RepoGroupModel()._delete_filesystem_group(repo_group)
325 325 raise
326 326
327 327 def update_permissions(
328 328 self, repo_group, perm_additions=None, perm_updates=None,
329 329 perm_deletions=None, recursive=None, check_perms=True,
330 330 cur_user=None):
331 331 from rhodecode.model.repo import RepoModel
332 332 from rhodecode.lib.auth import HasUserGroupPermissionAny
333 333
334 334 if not perm_additions:
335 335 perm_additions = []
336 336 if not perm_updates:
337 337 perm_updates = []
338 338 if not perm_deletions:
339 339 perm_deletions = []
340 340
341 341 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
342 342
343 343 changes = {
344 344 'added': [],
345 345 'updated': [],
346 346 'deleted': []
347 347 }
348 348
349 349 def _set_perm_user(obj, user, perm):
350 350 if isinstance(obj, RepoGroup):
351 351 self.grant_user_permission(
352 352 repo_group=obj, user=user, perm=perm)
353 353 elif isinstance(obj, Repository):
354 354 # private repos will not allow to change the default
355 355 # permissions using recursive mode
356 356 if obj.private and user == User.DEFAULT_USER:
357 357 return
358 358
359 359 # we set group permission but we have to switch to repo
360 360 # permission
361 361 perm = perm.replace('group.', 'repository.')
362 362 RepoModel().grant_user_permission(
363 363 repo=obj, user=user, perm=perm)
364 364
365 365 def _set_perm_group(obj, users_group, perm):
366 366 if isinstance(obj, RepoGroup):
367 367 self.grant_user_group_permission(
368 368 repo_group=obj, group_name=users_group, perm=perm)
369 369 elif isinstance(obj, Repository):
370 370 # we set group permission but we have to switch to repo
371 371 # permission
372 372 perm = perm.replace('group.', 'repository.')
373 373 RepoModel().grant_user_group_permission(
374 374 repo=obj, group_name=users_group, perm=perm)
375 375
376 376 def _revoke_perm_user(obj, user):
377 377 if isinstance(obj, RepoGroup):
378 378 self.revoke_user_permission(repo_group=obj, user=user)
379 379 elif isinstance(obj, Repository):
380 380 RepoModel().revoke_user_permission(repo=obj, user=user)
381 381
382 382 def _revoke_perm_group(obj, user_group):
383 383 if isinstance(obj, RepoGroup):
384 384 self.revoke_user_group_permission(
385 385 repo_group=obj, group_name=user_group)
386 386 elif isinstance(obj, Repository):
387 387 RepoModel().revoke_user_group_permission(
388 388 repo=obj, group_name=user_group)
389 389
390 390 # start updates
391 391 log.debug('Now updating permissions for %s in recursive mode:%s',
392 392 repo_group, recursive)
393 393
394 394 # initialize check function, we'll call that multiple times
395 395 has_group_perm = HasUserGroupPermissionAny(*req_perms)
396 396
397 397 for obj in repo_group.recursive_groups_and_repos():
398 398 # iterated obj is an instance of a repos group or repository in
399 399 # that group, recursive option can be: none, repos, groups, all
400 400 if recursive == 'all':
401 401 obj = obj
402 402 elif recursive == 'repos':
403 403 # skip groups, other than this one
404 404 if isinstance(obj, RepoGroup) and not obj == repo_group:
405 405 continue
406 406 elif recursive == 'groups':
407 407 # skip repos
408 408 if isinstance(obj, Repository):
409 409 continue
410 410 else: # recursive == 'none':
411 411 # DEFAULT option - don't apply to iterated objects
412 412 # also we do a break at the end of this loop. if we are not
413 413 # in recursive mode
414 414 obj = repo_group
415 415
416 416 change_obj = obj.get_api_data()
417 417
418 418 # update permissions
419 419 for member_id, perm, member_type in perm_updates:
420 420 member_id = int(member_id)
421 421 if member_type == 'user':
422 422 member_name = User.get(member_id).username
423 423 # this updates also current one if found
424 424 _set_perm_user(obj, user=member_id, perm=perm)
425 425 else: # set for user group
426 426 member_name = UserGroup.get(member_id).users_group_name
427 427 if not check_perms or has_group_perm(member_name,
428 428 user=cur_user):
429 429 _set_perm_group(obj, users_group=member_id, perm=perm)
430 430
431 431 changes['updated'].append(
432 432 {'change_obj': change_obj, 'type': member_type,
433 433 'id': member_id, 'name': member_name, 'new_perm': perm})
434 434
435 435 # set new permissions
436 436 for member_id, perm, member_type in perm_additions:
437 437 member_id = int(member_id)
438 438 if member_type == 'user':
439 439 member_name = User.get(member_id).username
440 440 _set_perm_user(obj, user=member_id, perm=perm)
441 441 else: # set for user group
442 442 # check if we have permissions to alter this usergroup
443 443 member_name = UserGroup.get(member_id).users_group_name
444 444 if not check_perms or has_group_perm(member_name,
445 445 user=cur_user):
446 446 _set_perm_group(obj, users_group=member_id, perm=perm)
447 447
448 448 changes['added'].append(
449 449 {'change_obj': change_obj, 'type': member_type,
450 450 'id': member_id, 'name': member_name, 'new_perm': perm})
451 451
452 452 # delete permissions
453 453 for member_id, perm, member_type in perm_deletions:
454 454 member_id = int(member_id)
455 455 if member_type == 'user':
456 456 member_name = User.get(member_id).username
457 457 _revoke_perm_user(obj, user=member_id)
458 458 else: # set for user group
459 459 # check if we have permissions to alter this usergroup
460 460 member_name = UserGroup.get(member_id).users_group_name
461 461 if not check_perms or has_group_perm(member_name,
462 462 user=cur_user):
463 463 _revoke_perm_group(obj, user_group=member_id)
464 464
465 465 changes['deleted'].append(
466 466 {'change_obj': change_obj, 'type': member_type,
467 467 'id': member_id, 'name': member_name, 'new_perm': perm})
468 468
469 469 # if it's not recursive call for all,repos,groups
470 470 # break the loop and don't proceed with other changes
471 471 if recursive not in ['all', 'repos', 'groups']:
472 472 break
473 473
474 474 return changes
475 475
476 476 def update(self, repo_group, form_data):
477 477 try:
478 478 repo_group = self._get_repo_group(repo_group)
479 479 old_path = repo_group.full_path
480 480
481 481 # change properties
482 482 if 'group_description' in form_data:
483 483 repo_group.group_description = form_data['group_description']
484 484
485 485 if 'enable_locking' in form_data:
486 486 repo_group.enable_locking = form_data['enable_locking']
487 487
488 488 if 'group_parent_id' in form_data:
489 489 parent_group = (
490 490 self._get_repo_group(form_data['group_parent_id']))
491 491 repo_group.group_parent_id = (
492 492 parent_group.group_id if parent_group else None)
493 493 repo_group.parent_group = parent_group
494 494
495 495 # mikhail: to update the full_path, we have to explicitly
496 496 # update group_name
497 497 group_name = form_data.get('group_name', repo_group.name)
498 498 repo_group.group_name = repo_group.get_new_name(group_name)
499 499
500 500 new_path = repo_group.full_path
501 501
502 502 if 'user' in form_data:
503 503 repo_group.user = User.get_by_username(form_data['user'])
504
504 repo_group.updated_on = datetime.datetime.now()
505 505 self.sa.add(repo_group)
506 506
507 507 # iterate over all members of this groups and do fixes
508 508 # set locking if given
509 509 # if obj is a repoGroup also fix the name of the group according
510 510 # to the parent
511 511 # if obj is a Repo fix it's name
512 512 # this can be potentially heavy operation
513 513 for obj in repo_group.recursive_groups_and_repos():
514 514 # set the value from it's parent
515 515 obj.enable_locking = repo_group.enable_locking
516 516 if isinstance(obj, RepoGroup):
517 517 new_name = obj.get_new_name(obj.name)
518 518 log.debug('Fixing group %s to new name %s',
519 519 obj.group_name, new_name)
520 520 obj.group_name = new_name
521 obj.updated_on = datetime.datetime.now()
521 522 elif isinstance(obj, Repository):
522 523 # we need to get all repositories from this new group and
523 524 # rename them accordingly to new group path
524 525 new_name = obj.get_new_name(obj.just_name)
525 526 log.debug('Fixing repo %s to new name %s',
526 527 obj.repo_name, new_name)
527 528 obj.repo_name = new_name
529 obj.updated_on = datetime.datetime.now()
528 530 self.sa.add(obj)
529 531
530 532 self._rename_group(old_path, new_path)
531 533
532 534 # Trigger update event.
533 535 events.trigger(events.RepoGroupUpdateEvent(repo_group))
534 536
535 537 return repo_group
536 538 except Exception:
537 539 log.error(traceback.format_exc())
538 540 raise
539 541
540 542 def delete(self, repo_group, force_delete=False, fs_remove=True):
541 543 repo_group = self._get_repo_group(repo_group)
542 544 if not repo_group:
543 545 return False
544 546 try:
545 547 self.sa.delete(repo_group)
546 548 if fs_remove:
547 549 self._delete_filesystem_group(repo_group, force_delete)
548 550 else:
549 551 log.debug('skipping removal from filesystem')
550 552
551 553 # Trigger delete event.
552 554 events.trigger(events.RepoGroupDeleteEvent(repo_group))
553 555 return True
554 556
555 557 except Exception:
556 558 log.error('Error removing repo_group %s', repo_group)
557 559 raise
558 560
559 561 def grant_user_permission(self, repo_group, user, perm):
560 562 """
561 563 Grant permission for user on given repository group, or update
562 564 existing one if found
563 565
564 566 :param repo_group: Instance of RepoGroup, repositories_group_id,
565 567 or repositories_group name
566 568 :param user: Instance of User, user_id or username
567 569 :param perm: Instance of Permission, or permission_name
568 570 """
569 571
570 572 repo_group = self._get_repo_group(repo_group)
571 573 user = self._get_user(user)
572 574 permission = self._get_perm(perm)
573 575
574 576 # check if we have that permission already
575 577 obj = self.sa.query(UserRepoGroupToPerm)\
576 578 .filter(UserRepoGroupToPerm.user == user)\
577 579 .filter(UserRepoGroupToPerm.group == repo_group)\
578 580 .scalar()
579 581 if obj is None:
580 582 # create new !
581 583 obj = UserRepoGroupToPerm()
582 584 obj.group = repo_group
583 585 obj.user = user
584 586 obj.permission = permission
585 587 self.sa.add(obj)
586 588 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
587 589 action_logger_generic(
588 590 'granted permission: {} to user: {} on repogroup: {}'.format(
589 591 perm, user, repo_group), namespace='security.repogroup')
590 592 return obj
591 593
592 594 def revoke_user_permission(self, repo_group, user):
593 595 """
594 596 Revoke permission for user on given repository group
595 597
596 598 :param repo_group: Instance of RepoGroup, repositories_group_id,
597 599 or repositories_group name
598 600 :param user: Instance of User, user_id or username
599 601 """
600 602
601 603 repo_group = self._get_repo_group(repo_group)
602 604 user = self._get_user(user)
603 605
604 606 obj = self.sa.query(UserRepoGroupToPerm)\
605 607 .filter(UserRepoGroupToPerm.user == user)\
606 608 .filter(UserRepoGroupToPerm.group == repo_group)\
607 609 .scalar()
608 610 if obj:
609 611 self.sa.delete(obj)
610 612 log.debug('Revoked perm on %s on %s', repo_group, user)
611 613 action_logger_generic(
612 614 'revoked permission from user: {} on repogroup: {}'.format(
613 615 user, repo_group), namespace='security.repogroup')
614 616
615 617 def grant_user_group_permission(self, repo_group, group_name, perm):
616 618 """
617 619 Grant permission for user group on given repository group, or update
618 620 existing one if found
619 621
620 622 :param repo_group: Instance of RepoGroup, repositories_group_id,
621 623 or repositories_group name
622 624 :param group_name: Instance of UserGroup, users_group_id,
623 625 or user group name
624 626 :param perm: Instance of Permission, or permission_name
625 627 """
626 628 repo_group = self._get_repo_group(repo_group)
627 629 group_name = self._get_user_group(group_name)
628 630 permission = self._get_perm(perm)
629 631
630 632 # check if we have that permission already
631 633 obj = self.sa.query(UserGroupRepoGroupToPerm)\
632 634 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
633 635 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
634 636 .scalar()
635 637
636 638 if obj is None:
637 639 # create new
638 640 obj = UserGroupRepoGroupToPerm()
639 641
640 642 obj.group = repo_group
641 643 obj.users_group = group_name
642 644 obj.permission = permission
643 645 self.sa.add(obj)
644 646 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
645 647 action_logger_generic(
646 648 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
647 649 perm, group_name, repo_group), namespace='security.repogroup')
648 650 return obj
649 651
650 652 def revoke_user_group_permission(self, repo_group, group_name):
651 653 """
652 654 Revoke permission for user group on given repository group
653 655
654 656 :param repo_group: Instance of RepoGroup, repositories_group_id,
655 657 or repositories_group name
656 658 :param group_name: Instance of UserGroup, users_group_id,
657 659 or user group name
658 660 """
659 661 repo_group = self._get_repo_group(repo_group)
660 662 group_name = self._get_user_group(group_name)
661 663
662 664 obj = self.sa.query(UserGroupRepoGroupToPerm)\
663 665 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
664 666 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
665 667 .scalar()
666 668 if obj:
667 669 self.sa.delete(obj)
668 670 log.debug('Revoked perm to %s on %s', repo_group, group_name)
669 671 action_logger_generic(
670 672 'revoked permission from usergroup: {} on repogroup: {}'.format(
671 673 group_name, repo_group), namespace='security.repogroup')
672 674
673 675 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
674 676 super_user_actions=False):
675 677
676 678 from pyramid.threadlocal import get_current_request
677 679 _render = get_current_request().get_partial_renderer(
678 680 'data_table/_dt_elements.mako')
679 681 c = _render.get_call_context()
680 682 h = _render.get_helpers()
681 683
682 684 def quick_menu(repo_group_name):
683 685 return _render('quick_repo_group_menu', repo_group_name)
684 686
685 687 def repo_group_lnk(repo_group_name):
686 688 return _render('repo_group_name', repo_group_name)
687 689
690 def last_change(last_change):
691 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
692 last_change = last_change + datetime.timedelta(seconds=
693 (datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
694 return _render("last_change", last_change)
695
688 696 def desc(desc, personal):
689 697 prefix = h.escaped_stylize(u'[personal] ') if personal else ''
690 698
691 699 if c.visual.stylify_metatags:
692 700 desc = h.urlify_text(prefix + h.escaped_stylize(desc))
693 701 else:
694 702 desc = h.urlify_text(prefix + h.html_escape(desc))
695 703
696 704 return _render('repo_group_desc', desc)
697 705
698 706 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
699 707 return _render(
700 708 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
701 709
702 710 def repo_group_name(repo_group_name, children_groups):
703 711 return _render("repo_group_name", repo_group_name, children_groups)
704 712
705 713 def user_profile(username):
706 714 return _render('user_profile', username)
707 715
708 716 repo_group_data = []
709 717 for group in repo_group_list:
710 718
711 719 row = {
712 720 "menu": quick_menu(group.group_name),
713 721 "name": repo_group_lnk(group.group_name),
714 722 "name_raw": group.group_name,
723 "last_change": last_change(group.last_db_change),
724 "last_change_raw": datetime_to_time(group.last_db_change),
715 725 "desc": desc(group.description_safe, group.personal),
716 726 "top_level_repos": 0,
717 727 "owner": user_profile(group.user.username)
718 728 }
719 729 if admin:
720 730 repo_count = group.repositories.count()
721 731 children_groups = map(
722 732 h.safe_unicode,
723 733 itertools.chain((g.name for g in group.parents),
724 734 (x.name for x in [group])))
725 735 row.update({
726 736 "action": repo_group_actions(
727 737 group.group_id, group.group_name, repo_count),
728 738 "top_level_repos": repo_count,
729 739 "name": repo_group_name(group.group_name, children_groups),
730 740
731 741 })
732 742 repo_group_data.append(row)
733 743
734 744 return repo_group_data
@@ -1,94 +1,97 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Repository groups administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 <ul class="links">
25 25 %if h.HasPermissionAny('hg.admin','hg.repogroup.create.true')():
26 26 <li>
27 27 <a href="${h.url('new_repo_group')}" class="btn btn-small btn-success">${_(u'Add Repository Group')}</a>
28 28 </li>
29 29 %endif
30 30 </ul>
31 31 </div>
32 32 <div id="repos_list_wrap">
33 33 <table id="group_list_table" class="display"></table>
34 34 </div>
35 35 </div>
36 36
37 37 <script>
38 38 $(document).ready(function() {
39 39
40 40 var get_datatable_count = function(){
41 41 var api = $('#group_list_table').dataTable().api();
42 42 $('#repo_group_count').text(api.page.info().recordsDisplay);
43 43 };
44 44
45 45 // repo group list
46 46 $('#group_list_table').DataTable({
47 47 data: ${c.data|n},
48 48 dom: 'rtp',
49 49 pageLength: ${c.visual.admin_grid_items},
50 50 order: [[ 0, "asc" ]],
51 51 columns: [
52 52 { data: {"_": "name",
53 53 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
54 54 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
55 55 { data: {"_": "desc",
56 56 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
57 { data: {"_": "last_change",
58 "sort": "last_change_raw",
59 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
57 60 { data: {"_": "top_level_repos",
58 61 "sort": "top_level_repos"}, title: "${_('Number of top level repositories')}" },
59 62 { data: {"_": "owner",
60 63 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
61 64 { data: {"_": "action",
62 65 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
63 66 ],
64 67 language: {
65 68 paginate: DEFAULT_GRID_PAGINATION,
66 69 emptyTable: _gettext("No repository groups available yet.")
67 70 },
68 71 "initComplete": function( settings, json ) {
69 72 get_datatable_count();
70 73 quick_repo_menu();
71 74 }
72 75 });
73 76
74 77 // update the counter when doing search
75 78 $('#group_list_table').on( 'search.dt', function (e,settings) {
76 79 get_datatable_count();
77 80 });
78 81
79 82 // filter, filter both grids
80 83 $('#q_filter').on( 'keyup', function () {
81 84
82 85 var repo_group_api = $('#group_list_table').dataTable().api();
83 86 repo_group_api
84 87 .columns(0)
85 88 .search(this.value)
86 89 .draw();
87 90 });
88 91
89 92 // refilter table if page load via back button
90 93 $("#q_filter").trigger('keyup');
91 94 });
92 95 </script>
93 96 </%def>
94 97
@@ -1,170 +1,173 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="main()">
4 4 <div class="box">
5 5 <!-- box / title -->
6 6 <div class="title">
7 7 <div class="block-left breadcrumbs">
8 8 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
9 9 ${self.breadcrumbs()}
10 10 <span id="match_container" style="display:none">&raquo; <span id="match_count">0</span> ${_('matches')}</span>
11 11 </div>
12 12 %if c.rhodecode_user.username != h.DEFAULT_USER:
13 13 <div class="block-right">
14 14 <%
15 15 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
16 16 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
17 17 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
18 18 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
19 19
20 20 gr_name = c.repo_group.group_name if c.repo_group else None
21 21 # create repositories with write permission on group is set to true
22 22 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
23 23 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
24 24 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
25 25 %>
26 26
27 27 %if not c.repo_group:
28 28 ## no repository group context here
29 29 %if is_admin or create_repo:
30 30 <a href="${h.url('new_repo')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
31 31 %endif
32 32
33 33 %if is_admin or create_repo_group:
34 34 <a href="${h.url('new_repo_group')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
35 35 %endif
36 36 %else:
37 37 ##we're inside other repository group other terms apply
38 38 %if is_admin or group_admin or (group_write and create_on_write):
39 39 <a href="${h.url('new_repo',parent_group=c.repo_group.group_id)}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
40 40 %endif
41 41 %if is_admin or group_admin:
42 42 <a href="${h.url('new_repo_group', parent_group=c.repo_group.group_id)}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
43 43 %endif
44 44 %if is_admin or group_admin:
45 45 <a href="${h.url('edit_repo_group',group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
46 46 %endif
47 47 %endif
48 48 </div>
49 49 %endif
50 50 </div>
51 51 <!-- end box / title -->
52 52 <div class="table">
53 53 <div id="groups_list_wrap">
54 54 <table id="group_list_table" class="display"></table>
55 55 </div>
56 56 </div>
57 57
58 58 <div class="table">
59 59 <div id="repos_list_wrap">
60 60 <table id="repo_list_table" class="display"></table>
61 61 </div>
62 62 </div>
63 63 </div>
64 64 <script>
65 65 $(document).ready(function() {
66 66
67 67 var get_datatable_count = function() {
68 68 var api = $('#repo_list_table').dataTable().api();
69 69 var pageInfo = api.page.info();
70 70 var repos = pageInfo.recordsDisplay;
71 71 var reposTotal = pageInfo.recordsTotal;
72 72
73 73 api = $('#group_list_table').dataTable().api();
74 74 pageInfo = api.page.info();
75 75 var repoGroups = pageInfo.recordsDisplay;
76 76 var repoGroupsTotal = pageInfo.recordsTotal;
77 77
78 78 if (repoGroups !== repoGroupsTotal) {
79 79 $('#match_count').text(repos+repoGroups);
80 80 }
81 81 if (repos !== reposTotal) {
82 82 $('#match_container').show();
83 83 }
84 84 if ($('#q_filter').val() === '') {
85 85 $('#match_container').hide();
86 86 }
87 87 };
88 88
89 89 // repo group list
90 90 $('#group_list_table').DataTable({
91 91 data: ${c.repo_groups_data|n},
92 92 dom: 'rtp',
93 93 pageLength: ${c.visual.dashboard_items},
94 94 order: [[ 0, "asc" ]],
95 95 columns: [
96 96 { data: {"_": "name",
97 97 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
98 98 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
99 99 { data: {"_": "desc",
100 100 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
101 { data: {"_": "last_change",
102 "sort": "last_change_raw",
103 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
101 104 { data: {"_": "owner",
102 105 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
103 106 ],
104 107 language: {
105 108 paginate: DEFAULT_GRID_PAGINATION,
106 109 emptyTable: _gettext("No repository groups available yet.")
107 110 },
108 111 "drawCallback": function( settings, json ) {
109 112 timeagoActivate();
110 113 quick_repo_menu();
111 114 }
112 115 });
113 116
114 117 // repo list
115 118 $('#repo_list_table').DataTable({
116 119 data: ${c.repos_data|n},
117 120 dom: 'rtp',
118 121 order: [[ 0, "asc" ]],
119 122 pageLength: ${c.visual.dashboard_items},
120 123 columns: [
121 124 { data: {"_": "name",
122 125 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
123 126 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
124 127 { data: {"_": "desc",
125 128 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
126 129 { data: {"_": "last_change",
127 130 "sort": "last_change_raw",
128 131 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
129 132 { data: {"_": "last_changeset",
130 133 "sort": "last_changeset_raw",
131 134 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
132 135 { data: {"_": "owner",
133 136 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
134 137 ],
135 138 language: {
136 139 paginate: DEFAULT_GRID_PAGINATION,
137 140 emptyTable: _gettext("No repositories available yet.")
138 141 },
139 142 "drawCallback": function( settings, json ) {
140 143 timeagoActivate();
141 144 quick_repo_menu();
142 145 }
143 146 });
144 147
145 148 // update the counter when doing search
146 149 $('#repo_list_table, #group_list_table').on( 'search.dt', function (e,settings) {
147 150 get_datatable_count();
148 151 });
149 152
150 153 // filter, filter both grids
151 154 $('#q_filter').on( 'keyup', function () {
152 155 var repo_api = $('#repo_list_table').dataTable().api();
153 156 repo_api
154 157 .columns( 0 )
155 158 .search( this.value )
156 159 .draw();
157 160
158 161 var repo_group_api = $('#group_list_table').dataTable().api();
159 162 repo_group_api
160 163 .columns( 0 )
161 164 .search( this.value )
162 165 .draw();
163 166 });
164 167
165 168 // refilter table if page load via back button
166 169 $("#q_filter").trigger('keyup');
167 170
168 171 });
169 172 </script>
170 173 </%def>
General Comments 0
You need to be logged in to leave comments. Login now