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