feature: implements repo deletion mechanism

This commit is contained in:
ievgenii vdovenko 2025-07-30 12:41:58 +02:00
parent 203936cbd8
commit d4cb542581
6 changed files with 56 additions and 32 deletions

View file

@ -1371,8 +1371,9 @@ def delete_repo(request, apiuser, repoid, forks=Optional("")):
_forks_msg = " " + f"Deleted {len(_forks)} forks"
elif _forks:
raise JSONRPCError(f"Cannot delete `{repo.repo_name}` it still contains attached forks")
old_data = repo.get_api_data()
RepoModel().delete(repo, forks=forks)
RepoModel().delete(repo, forks=RepoModel.ForkAction(handle_forks))
repo = audit_logger.RepoWrap(repo_id=None, repo_name=repo.repo_name)

View file

@ -124,11 +124,8 @@ class RepoSettingsAdvancedView(RepoAppView):
because of attached forks or other errors.
"""
_ = self.request.translate
handle_forks = self.request.POST.get("forks", None)
if handle_forks == "detach_forks":
handle_forks = "detach"
elif handle_forks == "delete_forks":
handle_forks = "delete"
fork_action = self._get_fork_action()
repo_advanced_url = h.route_path("edit_repo_advanced", repo_name=self.db_repo_name, _anchor="advanced-delete")
try:
@ -137,13 +134,13 @@ class RepoSettingsAdvancedView(RepoAppView):
delete_cache = True
self._invalidate_remote_cache(delete=delete_cache)
RepoModel().delete(self.db_repo, forks=handle_forks)
RepoModel().delete(self.db_repo, forks=fork_action)
_forks = self.db_repo.forks.count()
if _forks and handle_forks:
if handle_forks == "detach_forks":
if _forks and fork_action:
if fork_action is RepoModel.ForkAction.DETACH:
h.flash(_("Detached %s forks") % _forks, category="success")
elif handle_forks == "delete_forks":
elif fork_action is RepoModel.ForkAction.DELETE:
h.flash(_("Deleted %s forks") % _forks, category="success")
repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name)
@ -199,6 +196,16 @@ class RepoSettingsAdvancedView(RepoAppView):
raise HTTPFound(h.route_path("home"))
def _get_fork_action(self):
fork_action = self.request.POST.get("forks", None)
if fork_action == "detach_forks":
return RepoModel.ForkAction.DETACH
if fork_action == "delete_forks":
return RepoModel.ForkAction.DELETE
return RepoModel.ForkAction.UNKNOWN
def _invalidate_remote_cache(self, delete: bool):
log.debug(f"Invalidating remote cache, for repository: {self.db_repo_name}")
scm_repo = self.db_repo.scm_instance(cache=False)

View file

@ -688,7 +688,7 @@ def repo2db_cleanup(skip_repos=None, skip_groups=None):
if not instance:
log.debug("Removing non-existing repository found in db `%s`", db_repo_name)
try:
RepoModel(sa).delete(db_repo, forks="detach", fs_remove=False, call_events=False)
RepoModel(sa).delete(db_repo, forks=RepoModel.ForkAction.DETACH, fs_remove=False, call_events=False)
sa.commit()
removed.append(db_repo_name)
except Exception:

View file

@ -23,6 +23,8 @@ import time
import logging
import traceback
import datetime
from enum import StrEnum, auto
from typing import Optional
from sqlalchemy.orm import aliased
from zope.cachedescriptors.property import Lazy as LazyProperty
@ -76,6 +78,15 @@ log = logging.getLogger(__name__)
class RepoModel(BaseModel):
cls = Repository
class ForkAction(StrEnum):
DELETE = auto()
DETACH = auto()
UNKNOWN = auto()
@classmethod
def _missing_(cls, value):
return cls(cls.UNKNOWN)
def _get_user_group(self, users_group):
return self._get_instance(UserGroup, users_group, callback=UserGroup.get_by_group_name)
@ -749,17 +760,20 @@ class RepoModel(BaseModel):
raise
def delete(
self, repo, forks=None, pull_requests=None, artifacts=None, fs_remove=True, cur_user=None, call_events=True
):
self,
repo: Repository,
forks: ForkAction = None, # left for backward compatibility, do not use it
fs_remove: bool = True,
cur_user: User = None,
call_events: bool = True,
) -> bool:
"""
Delete given repository, forks parameter defines what do do with
attached forks. Throws AttachedForksError if deleted repo has attached
forks
:param repo:
:param forks: str 'delete' or 'detach'
:param pull_requests: str 'delete' or None
:param artifacts: str 'delete' or None
:param forks: ForkAction 'delete' or 'detach', NOTE: left for backward compatibility, do not use it
:param fs_remove: remove(archive) repo from filesystem
"""
if not cur_user:
@ -768,27 +782,21 @@ class RepoModel(BaseModel):
if not repo:
return False
if not forks: # left for backward compatibility
if forks is None or forks is self.ForkAction.UNKNOWN:
is_private = repo.private
forks = "delete" if is_private else "detach"
forks = self.ForkAction.DELETE if is_private else self.ForkAction.DETACH
if forks == "detach":
if forks is self.ForkAction.DETACH:
for r in repo.forks:
r.fork = None
self.sa.add(r)
elif forks == "delete":
elif forks is self.ForkAction.DELETE:
for r in repo.forks:
self.delete(r, forks="delete")
elif [f for f in repo.forks]:
self.delete(r, forks=self.ForkAction.DELETE)
elif repo.forks.first() is not None:
raise AttachedForksError()
# check for pull requests
pr_sources = repo.pull_requests_source
pr_targets = repo.pull_requests_target
artifacts = repo.artifacts
# FIXME: continue here
for pr in itertools.chain(pr_sources, pr_targets, artifacts):
self.sa.delete(pr)
self._delete_repo_related_assets(repo)
old_repo_dict = repo.get_dict()
if call_events:
@ -815,6 +823,14 @@ class RepoModel(BaseModel):
return True
def _delete_repo_related_assets(self, repo):
pr_sources = repo.pull_requests_source
pr_targets = repo.pull_requests_target
artifacts = repo.artifacts
logs = repo.logs
for obj_to_remove in itertools.chain(pr_sources, pr_targets, artifacts, logs):
self.sa.delete(obj_to_remove)
def grant_user_permission(self, repo, user, perm):
"""
Grant permission for user on given repository, or update existing one

View file

@ -294,7 +294,7 @@ class Fixture(object):
return r
def destroy_repo(self, repo_name, **kwargs):
RepoModel().delete(repo_name, pull_requests="delete", artifacts="delete", **kwargs)
RepoModel().delete(repo_name, **kwargs)
Session().commit()
def destroy_repo_on_filesystem(self, repo_name):

View file

@ -61,7 +61,7 @@ class TestRepoModel(object):
fork_of_fork = backend.create_fork()
Session().commit()
RepoModel().delete(repo=repo, forks="delete")
RepoModel().delete(repo=repo, forks=RepoModel.ForkAction.DELETE)
Session().commit()
assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None
@ -78,7 +78,7 @@ class TestRepoModel(object):
fork_of_fork = backend.create_fork()
Session().commit()
RepoModel().delete(repo=repo, forks="detach")
RepoModel().delete(repo=repo, forks=RepoModel.ForkAction.DETACH)
Session().commit()
assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None