Changeset - ee1da602b590
[Not reviewed]
stable
0 2 0
Thomas De Schampheleire - 4 years ago 2021-09-01 22:08:45
thomas.de_schampheleire@nokia.com
repo_groups: fix deletion of subgroups

Deletion of a repository group that has a parent group (i.e. is not at the
root of the repository group tree) failed as follows:

Traceback (most recent call last):
[...]
File ".../lib/python3.9/site-packages/tg/configurator/components/dispatch.py", line 114, in _call_controller
return controller(*remainder, **params)
File "<decorator-gen-5>", line 2, in delete

File "/home/tdescham/repo/contrib/kallithea/kallithea-release/kallithea/lib/auth.py", line 572, in __wrapper
return func(*fargs, **fkwargs)
File "/home/tdescham/repo/contrib/kallithea/kallithea-release/kallithea/controllers/admin/repo_groups.py", line 271, in delete
if gr.parent_group:
File ".../lib/python3.9/site-packages/sqlalchemy/orm/attributes.py", line 294, in __get__
return self.impl.get(instance_state(instance), dict_)
File ".../lib/python3.9/site-packages/sqlalchemy/orm/attributes.py", line 730, in get
value = self.callable_(state, passive)
File ".../lib/python3.9/site-packages/sqlalchemy/orm/strategies.py", line 717, in _load_for_state
raise orm_exc.DetachedInstanceError(
sqlalchemy.orm.exc.DetachedInstanceError: Parent instance <RepoGroup at 0x7f1f2664f4c0> is not bound to a Session; lazy load operation of attribute 'parent_group' cannot proceed (Background on this error at: http://sqlalche.me/e/13/bhk3)


In the reference 'gr.parent_group', 'gr' is an SQLAlchemy object referring
to the group being deleted, and 'gr.parent_group' is a lazy reference to its
parent group. The 'lazy' means that the parent group object is not loaded
automatically when 'gr' is assigned, but instead will be loaded on-the-fly
when the parent group is actually accessed. See [1] and [2] for more
information.

The problem was that the lazy 'parent_group' attribute was accessed _after_
deleting the database object it was part of.

Fix this by obtaining a handle to the parent group _before_ deleting the
subgroup.

Reported-by: André Klitzing (via mailing list)


[1] https://docs.sqlalchemy.org/en/13/errors.html#error-bhk3
[2] https://docs.sqlalchemy.org/en/13/orm/loading_relationships.html
2 files changed with 55 insertions and 3 deletions:
0 comments (0 inline, 0 general)
kallithea/controllers/admin/repo_groups.py
Show inline comments
 
@@ -242,12 +242,13 @@ class RepoGroupsController(base.BaseCont
 

	
 
        raise HTTPFound(location=url('edit_repo_group', group_name=group_name))
 

	
 
    @HasRepoGroupPermissionLevelDecorator('admin')
 
    def delete(self, group_name):
 
        gr = c.repo_group = db.RepoGroup.guess_instance(group_name)
 
        parent_group = gr.parent_group
 
        repos = gr.repositories.all()
 
        if repos:
 
            webutils.flash(_('This group contains %s repositories and cannot be '
 
                      'deleted') % len(repos), category='warning')
 
            raise HTTPFound(location=url('repos_groups'))
 

	
 
@@ -265,14 +266,14 @@ class RepoGroupsController(base.BaseCont
 
            # TODO: in future action_logger(, '', '', '')
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            webutils.flash(_('Error occurred during deletion of repository group %s')
 
                    % group_name, category='error')
 

	
 
        if gr.parent_group:
 
            raise HTTPFound(location=url('repos_group_home', group_name=gr.parent_group.group_name))
 
        if parent_group:
 
            raise HTTPFound(location=url('repos_group_home', group_name=parent_group.group_name))
 
        raise HTTPFound(location=url('repos_groups'))
 

	
 
    def show_by_name(self, group_name):
 
        """
 
        This is a proxy that does a lookup group_name -> id, and shows
 
        the group by id view instead
kallithea/tests/functional/test_admin_repo_groups.py
Show inline comments
 
from kallithea.model import meta
 
from kallithea.model import db, meta
 
from kallithea.model.repo_group import RepoGroupModel
 
from kallithea.tests import base
 
from kallithea.tests.fixture import Fixture
 

	
 

	
 
fixture = Fixture()
 
@@ -94,6 +94,57 @@ class TestRepoGroupsController(base.Test
 
                                 fixture._get_repo_group_create_params(group_name=swapped_group_name,
 
                                                                 _session_csrf_secret_token=self.session_csrf_secret_token()))
 
        response.mustcontain('already exists')
 

	
 
        RepoGroupModel().delete(group_name)
 
        meta.Session().commit()
 

	
 
    def test_subgroup_deletion(self):
 
        self.log_user()
 
        parent = None
 
        parent_name = 'parent'
 
        sub = None
 
        sub_name = 'sub'
 
        sub_path = 'parent/sub'
 

	
 
        try:
 
            # create parent group
 
            assert db.RepoGroup.guess_instance(parent_name) is None
 
            response = self.app.post(
 
                base.url('repos_groups'),
 
                fixture._get_repo_group_create_params(
 
                    group_name=parent_name,
 
                    _session_csrf_secret_token=self.session_csrf_secret_token()
 
                )
 
            )
 
            parent = db.RepoGroup.guess_instance(parent_name)
 
            assert parent is not None
 

	
 
            # create sub group
 
            assert db.RepoGroup.guess_instance(sub_path) is None
 
            response = self.app.post(
 
                base.url('repos_groups'),
 
                fixture._get_repo_group_create_params(
 
                    group_name=sub_name,
 
                    parent_group_id=parent.group_id,
 
                    _session_csrf_secret_token=self.session_csrf_secret_token()
 
                )
 
            )
 
            sub = db.RepoGroup.guess_instance(sub_path)
 
            assert sub is not None
 

	
 
            # delete sub group
 
            response = self.app.post(
 
                base.url('delete_repo_group', group_name=sub_path),
 
                params={
 
                    '_session_csrf_secret_token': self.session_csrf_secret_token()
 
                },
 
            )
 
            sub = db.RepoGroup.guess_instance(sub_path)
 
            assert sub is None
 

	
 
        finally:
 
            if sub:
 
                RepoGroupModel().delete(sub)
 
            if parent:
 
                RepoGroupModel().delete(parent)
 
            meta.Session().commit()
0 comments (0 inline, 0 general)