diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -19,10 +19,10 @@ def make_map(config): always_scan=config['debug']) rmap.minimization = False rmap.explicit = False - + from rhodecode.lib.utils import is_valid_repo from rhodecode.lib.utils import is_valid_repos_group - + def check_repo(environ, match_dict): """ check for valid repository for proper 404 handling @@ -30,7 +30,7 @@ def make_map(config): :param environ: :param match_dict: """ - + repo_name = match_dict.get('repo_name') return is_valid_repo(repo_name, config['base_path']) @@ -42,7 +42,7 @@ def make_map(config): :param match_dict: """ repos_group_name = match_dict.get('group_name') - + return is_valid_repos_group(repos_group_name, config['base_path']) @@ -333,13 +333,13 @@ def make_map(config): # REPOSITORY ROUTES #========================================================================== rmap.connect('summary_home', '/{repo_name:.*}', - controller='summary', + controller='summary', conditions=dict(function=check_repo)) - -# rmap.connect('repo_group_home', '/{group_name:.*}', -# controller='admin/repos_groups',action="show_by_name", -# conditions=dict(function=check_group)) - + + rmap.connect('repos_group_home', '/{group_name:.*}', + controller='admin/repos_groups', action="show_by_name", + conditions=dict(function=check_group)) + rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}', controller='changeset', revision='tip', conditions=dict(function=check_repo)) diff --git a/rhodecode/controllers/admin/repos_groups.py b/rhodecode/controllers/admin/repos_groups.py --- a/rhodecode/controllers/admin/repos_groups.py +++ b/rhodecode/controllers/admin/repos_groups.py @@ -33,14 +33,10 @@ class ReposGroupsController(BaseControll def __load_defaults(self): c.repo_groups = [('', '')] - parents_link = lambda k: h.literal('»'.join( - map(lambda k: k.group_name, - k.parents + [k]) - ) - ) + parents_link = lambda k: h.literal('»'.join(k)) - c.repo_groups.extend([(x.group_id, parents_link(x)) for \ - x in self.sa.query(Group).all()]) + c.repo_groups.extend([(x.group_id, parents_link(x.full_path_splitted)) + for x in self.sa.query(Group).all()]) c.repo_groups = sorted(c.repo_groups, key=lambda t: t[1].split('»')[0]) @@ -58,6 +54,8 @@ class ReposGroupsController(BaseControll data = repo_group.get_dict() + data['group_name'] = repo_group.name + return data @HasPermissionAnyDecorator('hg.admin') @@ -176,6 +174,10 @@ class ReposGroupsController(BaseControll return redirect(url('repos_groups')) + def show_by_name(self, group_name): + id_ = Group.get_by_group_name(group_name).group_id + return self.show(id_) + def show(self, id, format='html'): """GET /repos_groups/id: Show a specific item""" # url('repos_group', id=ID) diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py --- a/rhodecode/lib/utils.py +++ b/rhodecode/lib/utils.py @@ -360,16 +360,19 @@ def map_groups(groups): parent = None group = None - for lvl, group_name in enumerate(groups[:-1]): + + # last element is repo in nested groups structure + groups = groups[:-1] + + for lvl, group_name in enumerate(groups): + group_name = '/'.join(groups[:lvl] + [group_name]) group = sa.query(Group).filter(Group.group_name == group_name).scalar() if group is None: group = Group(group_name, parent) sa.add(group) sa.commit() - parent = group - return group diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -126,7 +126,8 @@ class BaseModel(object): @classmethod def get(cls, id_): - return Session.query(cls).get(id_) + if id_: + return Session.query(cls).get(id_) @classmethod def delete(cls, id_): @@ -721,6 +722,10 @@ class Group(Base, BaseModel): def url_sep(cls): return '/' + @classmethod + def get_by_group_name(cls, group_name): + return cls.query().filter(cls.group_name == group_name).scalar() + @property def parents(self): parents_recursion_limit = 5 @@ -750,9 +755,16 @@ class Group(Base, BaseModel): return Session.query(Group).filter(Group.parent_group == self) @property + def name(self): + return self.group_name.split(Group.url_sep())[-1] + + @property def full_path(self): - return Group.url_sep().join([g.group_name for g in self.parents] + - [self.group_name]) + return self.group_name + + @property + def full_path_splitted(self): + return self.group_name.split(Group.url_sep()) @property def repositories(self): @@ -771,6 +783,17 @@ class Group(Base, BaseModel): return cnt + children_count(self) + + def get_new_name(self, group_name): + """ + returns new full group name based on parent and new name + + :param group_name: + """ + path_prefix = self.parent_group.full_path_splitted if self.parent_group else [] + return Group.url_sep().join(path_prefix + [group_name]) + + class Permission(Base, BaseModel): __tablename__ = 'permissions' __table_args__ = {'extend_existing':True} diff --git a/rhodecode/model/repos_group.py b/rhodecode/model/repos_group.py --- a/rhodecode/model/repos_group.py +++ b/rhodecode/model/repos_group.py @@ -50,7 +50,7 @@ class ReposGroupModel(BaseModel): q = RhodeCodeUi.get_by_key('/').one() return q.ui_value - def __create_group(self, group_name, parent_id): + def __create_group(self, group_name): """ makes repositories group on filesystem @@ -58,44 +58,30 @@ class ReposGroupModel(BaseModel): :param parent_id: """ - if parent_id: - paths = Group.get(parent_id).full_path.split(Group.url_sep()) - parent_path = os.sep.join(paths) - else: - parent_path = '' - - create_path = os.path.join(self.repos_path, parent_path, group_name) + create_path = os.path.join(self.repos_path, group_name) log.debug('creating new group in %s', create_path) if os.path.isdir(create_path): raise Exception('That directory already exists !') - os.makedirs(create_path) - - def __rename_group(self, old, old_parent_id, new, new_parent_id): + def __rename_group(self, old, new): """ Renames a group on filesystem :param group_name: """ + + if old == new: + log.debug('skipping group rename') + return + log.debug('renaming repos group from %s to %s', old, new) - if new_parent_id: - paths = Group.get(new_parent_id).full_path.split(Group.url_sep()) - new_parent_path = os.sep.join(paths) - else: - new_parent_path = '' - if old_parent_id: - paths = Group.get(old_parent_id).full_path.split(Group.url_sep()) - old_parent_path = os.sep.join(paths) - else: - old_parent_path = '' - - old_path = os.path.join(self.repos_path, old_parent_path, old) - new_path = os.path.join(self.repos_path, new_parent_path, new) + old_path = os.path.join(self.repos_path, old) + new_path = os.path.join(self.repos_path, new) log.debug('renaming repos paths from %s to %s', old_path, new_path) @@ -119,17 +105,16 @@ class ReposGroupModel(BaseModel): def create(self, form_data): try: new_repos_group = Group() - new_repos_group.group_name = form_data['group_name'] - new_repos_group.group_description = \ - form_data['group_description'] - new_repos_group.group_parent_id = form_data['group_parent_id'] + new_repos_group.group_description = form_data['group_description'] + new_repos_group.parent_group = Group.get(form_data['group_parent_id']) + new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name']) self.sa.add(new_repos_group) - self.__create_group(form_data['group_name'], - form_data['group_parent_id']) + self.__create_group(new_repos_group.group_name) self.sa.commit() + return new_repos_group except: log.error(traceback.format_exc()) self.sa.rollback() @@ -139,23 +124,21 @@ class ReposGroupModel(BaseModel): try: repos_group = Group.get(repos_group_id) - old_name = repos_group.group_name - old_parent_id = repos_group.group_parent_id + old_path = repos_group.full_path - repos_group.group_name = form_data['group_name'] - repos_group.group_description = \ - form_data['group_description'] - repos_group.group_parent_id = form_data['group_parent_id'] + #change properties + repos_group.group_description = form_data['group_description'] + repos_group.parent_group = Group.get(form_data['group_parent_id']) + repos_group.group_name = repos_group.get_new_name(form_data['group_name']) + + new_path = repos_group.full_path self.sa.add(repos_group) - if old_name != form_data['group_name'] or (old_parent_id != - form_data['group_parent_id']): - self.__rename_group(old=old_name, old_parent_id=old_parent_id, - new=form_data['group_name'], - new_parent_id=form_data['group_parent_id']) + self.__rename_group(old_path, new_path) self.sa.commit() + return repos_group except: log.error(traceback.format_exc()) self.sa.rollback() diff --git a/rhodecode/public/css/style.css b/rhodecode/public/css/style.css --- a/rhodecode/public/css/style.css +++ b/rhodecode/public/css/style.css @@ -564,6 +564,14 @@ padding:12px 9px 7px 24px; } +.groups_breadcrumbs a { + color: #fff; +} +.groups_breadcrumbs a:hover { + color: #bfe3ff; + text-decoration: none; +} + .quick_repo_menu{ background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important; cursor: pointer; diff --git a/rhodecode/templates/admin/repos_groups/repos_groups.html b/rhodecode/templates/admin/repos_groups/repos_groups.html --- a/rhodecode/templates/admin/repos_groups/repos_groups.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups.html @@ -5,12 +5,14 @@ <%def name="breadcrumbs()"> + ${_('Groups')} %if c.group.parent_group: - » ${h.link_to(c.group.parent_group.group_name, - h.url('repos_group',id=c.group.parent_group.group_id))} + » ${h.link_to(c.group.parent_group.name, + h.url('repos_group_home',group_name=c.group.parent_group.group_name))} %endif - » "${c.group.group_name}" ${_('with')} + » "${c.group.name}" ${_('with')} + <%def name="page_nav()"> diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html @@ -2,14 +2,14 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Edit repos group')} ${c.repos_group.group_name} - ${c.rhodecode_name} + ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> ${h.link_to(_('Admin'),h.url('admin_home'))} » ${h.link_to(_('Repos groups'),h.url('repos_groups'))} » - ${_('edit repos group')} "${c.repos_group.group_name}" + ${_('edit repos group')} "${c.repos_group.name}" <%def name="page_nav()"> diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_show.html b/rhodecode/templates/admin/repos_groups/repos_groups_show.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_show.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups_show.html @@ -44,14 +44,14 @@
${_('Repositories group')} - ${h.link_to(h.literal(' » '.join([g.group_name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))} + ${h.link_to(h.literal(' » '.join([g.name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))}
${gr.group_description} ${gr.repositories.count()} ${h.form(url('repos_group', id=gr.group_id),method='delete')} - ${h.submit('remove_%s' % gr.group_name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group')+"');")} + ${h.submit('remove_%s' % gr.name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group')+"');")} ${h.end_form()} diff --git a/rhodecode/templates/index_base.html b/rhodecode/templates/index_base.html --- a/rhodecode/templates/index_base.html +++ b/rhodecode/templates/index_base.html @@ -35,7 +35,7 @@
${_('Repositories group')} - ${h.link_to(gr.group_name,url('repos_group',id=gr.group_id))} + ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
${gr.group_description} diff --git a/rhodecode/tests/test_models.py b/rhodecode/tests/test_models.py --- a/rhodecode/tests/test_models.py +++ b/rhodecode/tests/test_models.py @@ -1,2 +1,115 @@ +import os import unittest from rhodecode.tests import * + +from rhodecode.model.repos_group import ReposGroupModel +from rhodecode.model.db import Group +from sqlalchemy.exc import IntegrityError + +class TestReposGroups(unittest.TestCase): + + def setUp(self): + self.g1 = self.__make_group('test1', skip_if_exists=True) + self.g2 = self.__make_group('test2', skip_if_exists=True) + self.g3 = self.__make_group('test3', skip_if_exists=True) + + def tearDown(self): + print 'out' + + def __check_path(self, *path): + path = [TESTS_TMP_PATH] + list(path) + path = os.path.join(*path) + return os.path.isdir(path) + + def _check_folders(self): + print os.listdir(TESTS_TMP_PATH) + + def __make_group(self, path, desc='desc', parent_id=None, + skip_if_exists=False): + + gr = Group.get_by_group_name(path) + if gr and skip_if_exists: + return gr + + form_data = dict(group_name=path, + group_description=desc, + group_parent_id=parent_id) + gr = ReposGroupModel().create(form_data) + return gr + + def __delete_group(self, id_): + ReposGroupModel().delete(id_) + + + def __update_group(self, id_, path, desc='desc', parent_id=None): + form_data = dict(group_name=path, + group_description=desc, + group_parent_id=parent_id) + + gr = ReposGroupModel().update(id_, form_data) + return gr + + def test_create_group(self): + g = self.__make_group('newGroup') + self.assertEqual(g.full_path, 'newGroup') + + self.assertTrue(self.__check_path('newGroup')) + + + def test_create_same_name_group(self): + self.assertRaises(IntegrityError, lambda:self.__make_group('newGroup')) + + + def test_same_subgroup(self): + sg1 = self.__make_group('sub1', parent_id=self.g1.group_id) + self.assertEqual(sg1.parent_group, self.g1) + self.assertEqual(sg1.full_path, 'test1/sub1') + self.assertTrue(self.__check_path('test1', 'sub1')) + + ssg1 = self.__make_group('subsub1', parent_id=sg1.group_id) + self.assertEqual(ssg1.parent_group, sg1) + self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1') + self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1')) + + + def test_remove_group(self): + sg1 = self.__make_group('deleteme') + self.__delete_group(sg1.group_id) + + self.assertEqual(Group.get(sg1.group_id), None) + self.assertFalse(self.__check_path('deteteme')) + + sg1 = self.__make_group('deleteme', parent_id=self.g1.group_id) + self.__delete_group(sg1.group_id) + + self.assertEqual(Group.get(sg1.group_id), None) + self.assertFalse(self.__check_path('test1', 'deteteme')) + + + def test_rename_single_group(self): + sg1 = self.__make_group('initial') + + new_sg1 = self.__update_group(sg1.group_id, 'after') + self.assertTrue(self.__check_path('after')) + self.assertEqual(Group.get_by_group_name('initial'), None) + + + def test_update_group_parent(self): + + sg1 = self.__make_group('initial', parent_id=self.g1.group_id) + + new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id) + self.assertTrue(self.__check_path('test1', 'after')) + self.assertEqual(Group.get_by_group_name('test1/initial'), None) + + + new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id) + self.assertTrue(self.__check_path('test3', 'after')) + self.assertEqual(Group.get_by_group_name('test3/initial'), None) + + + new_sg1 = self.__update_group(sg1.group_id, 'hello') + self.assertTrue(self.__check_path('hello')) + + self.assertEqual(Group.get_by_group_name('hello'), new_sg1) +