@@ -7,48 +7,50 @@ Changelog
1.3.4 (**2012-XX-XX**)
----------------------
:status: in-progress
:branch: beta
news
++++
- Whoosh logging is now controlled by the .ini files logging setup
- added clone-url into edit form on /settings page
- added help text into repo add/edit forms
- created rcextensions module with additional mappings (ref #322) and
post push/pull/create repo hooks callbacks
- implemented #377 Users view for his own permissions on account page
- #399 added inheritance of permissions for users group on repos groups
- #401 repository group is automatically pre-selected when adding repos
inside a repository group
- added alternative HTTP 403 response when client failed to authenticate. Helps
solving issues with Mercurial and LDAP
- #402 removed group prefix from repository name when listing repositories
inside a group
- added gravatars into permission view and permissions autocomplete
- #347 when running multiple RhodeCode instances, properly invalidates cache
for all registered servers
fixes
+++++
- fixed #390 cache invalidation problems on repos inside group
- fixed #385 clone by ID url was loosing proxy prefix in URL
- fixed some unicode problems with waitress
- fixed issue with escaping < and > in changeset commits
- fixed error occurring during recursive group creation in API
create_repo function
- fixed #393 py2.5 fixes for routes url generator
- fixed #397 Private repository groups shows up before login
- fixed #396 fixed problems with revoking users in nested groups
1.3.3 (**2012-03-02**)
@@ -30,49 +30,50 @@ import datetime
import traceback
import paste
import beaker
import tarfile
import shutil
from os.path import abspath
from os.path import dirname as dn, join as jn
from paste.script.command import Command, BadCommand
from mercurial import ui, config
from webhelpers.text import collapse, remove_formatting, strip_tags
from rhodecode.lib.vcs import get_backend
from rhodecode.lib.vcs.backends.base import BaseChangeset
from rhodecode.lib.vcs.utils.lazy import LazyProperty
from rhodecode.lib.vcs.utils.helpers import get_scm
from rhodecode.lib.vcs.exceptions import VCSError
from rhodecode.lib.caching_query import FromCache
from rhodecode.model import meta
from rhodecode.model.db import Repository, User, RhodeCodeUi, \
UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm
UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm,\
CacheInvalidation
from rhodecode.model.meta import Session
from rhodecode.model.repos_group import ReposGroupModel
from rhodecode.lib.utils2 import safe_str, safe_unicode
from rhodecode.lib.vcs.utils.fakemod import create_module
log = logging.getLogger(__name__)
REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
def recursive_replace(str_, replace=' '):
"""
Recursive replace of given sign to just one instance
:param str_: given string
:param replace: char to find and replace multiple instances
Examples::
>>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
'Mighty-Mighty-Bo-sstones'
if str_.find(replace * 2) == -1:
return str_
@@ -431,55 +432,61 @@ def repo2db_mapper(initial_repo_list, re
rm = RepoModel()
user = sa.query(User).filter(User.admin == True).first()
if user is None:
raise Exception('Missing administrative account !')
added = []
for name, repo in initial_repo_list.items():
group = map_groups(name)
if not rm.get_by_repo_name(name, cache=False):
log.info('repository %s not found creating default' % name)
added.append(name)
form_data = {
'repo_name': name,
'repo_name_full': name,
'repo_type': repo.alias,
'description': repo.description \
if repo.description != 'unknown' else '%s repository' % name,
'private': False,
'group_id': getattr(group, 'group_id', None)
}
rm.create(form_data, user, just_db=True)
sa.commit()
removed = []
if remove_obsolete:
#remove from database those repositories that are not in the filesystem
# remove from database those repositories that are not in the filesystem
for repo in sa.query(Repository).all():
if repo.repo_name not in initial_repo_list.keys():
log.debug("Removing non existing repository found in db %s" %
repo.repo_name)
removed.append(repo.repo_name)
sa.delete(repo)
# clear cache keys
log.debug("Clearing cache keys now...")
CacheInvalidation.clear_cache()
return added, removed
# set cache regions for beaker so celery can utilise it
def add_cache(settings):
cache_settings = {'regions': None}
for key in settings.keys():
for prefix in ['beaker.cache.', 'cache.']:
if key.startswith(prefix):
name = key.split(prefix)[1].strip()
cache_settings[name] = settings[key].strip()
if cache_settings['regions']:
for region in cache_settings['regions'].split(','):
region = region.strip()
region_settings = {}
for key, value in cache_settings.items():
if key.startswith(region):
region_settings[key.split('.')[1]] = value
region_settings['expire'] = int(region_settings.get('expire',
60))
region_settings.setdefault('lock_dir',
cache_settings.get('lock_dir'))
region_settings.setdefault('data_dir',
cache_settings.get('data_dir'))
@@ -1021,100 +1021,117 @@ class UserFollowing(Base, BaseModel):
follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
follows_repository = relationship('Repository', order_by='Repository.repo_name')
@classmethod
def get_repo_followers(cls, repo_id):
return cls.query().filter(cls.follows_repo_id == repo_id)
class CacheInvalidation(Base, BaseModel):
__tablename__ = 'cache_invalidation'
__table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
def __init__(self, cache_key, cache_args=''):
self.cache_key = cache_key
self.cache_args = cache_args
self.cache_active = False
def __repr__(self):
return "<%s('%s:%s')>" % (self.__class__.__name__,
self.cache_id, self.cache_key)
def clear_cache(cls):
cls.query().delete()
def _get_key(cls, key):
Wrapper for generating a key
Wrapper for generating a key, together with a prefix
:param key:
import rhodecode
prefix = ''
iid = rhodecode.CONFIG.get('instance_id')
if iid:
prefix = iid
return "%s%s" % (prefix, key)
return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
def get_by_key(cls, key):
return cls.query().filter(cls.cache_key == key).scalar()
def _get_or_create_key(cls, key, prefix, org_key):
inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
if not inv_obj:
try:
inv_obj = CacheInvalidation(key, org_key)
Session.add(inv_obj)
Session.commit()
except Exception:
log.error(traceback.format_exc())
Session.rollback()
return inv_obj
def invalidate(cls, key):
Returns Invalidation object if this given key should be invalidated
None otherwise. `cache_active = False` means that this cache
state is not valid and needs to be invalidated
return cls.query()\
.filter(CacheInvalidation.cache_key == key)\
.filter(CacheInvalidation.cache_active == False)\
.scalar()
key, _prefix, _org_key = cls._get_key(key)
inv = cls._get_or_create_key(key, _prefix, _org_key)
if inv and inv.cache_active is False:
return inv
def set_invalidate(cls, key):
Mark this Cache key for invalidation
log.debug('marking %s for invalidation' % key)
inv_obj = Session.query(cls)\
.filter(cls.cache_key == key).scalar()
if inv_obj:
inv_obj.cache_active = False
else:
log.debug('cache key not found in invalidation db -> creating one')
inv_obj = CacheInvalidation(key)
inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
_org_key))
for inv_obj in inv_objs:
def set_valid(cls, key):
Mark this cache key as active and currently cached
inv_obj = cls.get_by_key(key)
inv_obj.cache_active = True
class ChangesetComment(Base, BaseModel):
__tablename__ = 'changeset_comments'
__table_args__ = ({'extend_existing': True},)
comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
revision = Column('revision', String(40), nullable=False)
@@ -214,55 +214,55 @@ class ScmModel(BaseModel):
backend instance and fill that backed with information from database
:param all_repos: list of repository names as strings
give specific repositories list, good for filtering
if all_repos is None:
all_repos = self.sa.query(Repository)\
.filter(Repository.group_id == None)\
.order_by(Repository.repo_name).all()
repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
order_by=sort_key)
return repo_iter
def get_repos_groups(self, all_groups=None):
if all_groups is None:
all_groups = RepoGroup.query()\
.filter(RepoGroup.group_parent_id == None).all()
group_iter = GroupList(all_groups)
return group_iter
def mark_for_invalidation(self, repo_name):
"""Puts cache invalidation task into db for
Puts cache invalidation task into db for
further global cache invalidation
:param repo_name: this repo that should invalidation take place
CacheInvalidation.set_invalidate(repo_name)
CacheInvalidation.set_invalidate(repo_name + "_README")
def toggle_following_repo(self, follow_repo_id, user_id):
f = self.sa.query(UserFollowing)\
.filter(UserFollowing.follows_repo_id == follow_repo_id)\
.filter(UserFollowing.user_id == user_id).scalar()
if f is not None:
self.sa.delete(f)
action_logger(UserTemp(user_id),
'stopped_following_repo',
RepoTemp(follow_repo_id))
return
except:
raise
f = UserFollowing()
f.user_id = user_id
f.follows_repo_id = follow_repo_id
self.sa.add(f)
Status change: