From d392589e5c81be2b4112d340eb2077336ec93de4 Mon Sep 17 00:00:00 2001 From: ievgenii vdovenko Date: Thu, 31 Jul 2025 15:14:23 +0200 Subject: [PATCH] feature: makes forks and PR's link to clearly show admin what will be done --- rhodecode/api/views/repo_api.py | 4 +- .../views/repo_settings_advanced.py | 41 +++++------ rhodecode/i18n/en/LC_MESSAGES/rhodecode.po | 6 +- rhodecode/i18n/rhodecode.pot | 2 +- rhodecode/lib/utils.py | 5 +- rhodecode/model/repo.py | 71 ++++++++++++------- .../admin/repos/repo_edit_advanced.mako | 40 ++++------- rhodecode/tests/models/test_repos.py | 6 +- 8 files changed, 92 insertions(+), 83 deletions(-) diff --git a/rhodecode/api/views/repo_api.py b/rhodecode/api/views/repo_api.py index f624f608..4da17e7b 100644 --- a/rhodecode/api/views/repo_api.py +++ b/rhodecode/api/views/repo_api.py @@ -50,7 +50,7 @@ from rhodecode.model.comment import CommentsModel from rhodecode.model.db import Session, ChangesetStatus, RepositoryField, Repository, RepoGroup, ChangesetComment from rhodecode.model.permission import PermissionModel from rhodecode.model.pull_request import PullRequestModel -from rhodecode.model.repo import RepoModel +from rhodecode.model.repo import RepoModel, ForksAction from rhodecode.model.scm import ScmModel, RepoList from rhodecode.model.settings import SettingsModel, VcsSettingsModel from rhodecode.model import validation_schema @@ -1373,7 +1373,7 @@ def delete_repo(request, apiuser, repoid, forks=Optional("")): raise JSONRPCError(f"Cannot delete `{repo.repo_name}` it still contains attached forks") old_data = repo.get_api_data() - RepoModel().delete(repo, forks=RepoModel.ForkAction(handle_forks)) + RepoModel().delete(repo, forks_action=ForksAction(handle_forks)) repo = audit_logger.RepoWrap(repo_id=None, repo_name=repo.repo_name) diff --git a/rhodecode/apps/repository/views/repo_settings_advanced.py b/rhodecode/apps/repository/views/repo_settings_advanced.py index 7a43705a..f47934ea 100644 --- a/rhodecode/apps/repository/views/repo_settings_advanced.py +++ b/rhodecode/apps/repository/views/repo_settings_advanced.py @@ -31,7 +31,7 @@ from rhodecode.lib.utils2 import safe_int from rhodecode.lib.vcs import RepositoryError from rhodecode.model.db import Session, UserFollowing, User, Repository from rhodecode.model.permission import PermissionModel -from rhodecode.model.repo import RepoModel +from rhodecode.model.repo import RepoModel, ForksAction from rhodecode.model.scm import ScmModel log = logging.getLogger(__name__) @@ -56,6 +56,9 @@ class RepoSettingsAdvancedView(RepoAppView): c = self.load_default_context() c.active = "advanced" + c.fork_links = self._resolve_repo_forks_links() + c.pr_link = h.route_path("pullrequest_show_all", repo_name=self.db_repo_name) + c.default_user_id = User.get_default_user_id() c.in_public_journal = ( UserFollowing.query() @@ -80,6 +83,14 @@ class RepoSettingsAdvancedView(RepoAppView): return self._get_template_context(c) + def _resolve_repo_forks_links(self): + def resolve_fork_link(repo): + for fork in repo.forks: + yield h.route_path("repo_summary", repo_name=fork.repo_name) + yield from resolve_fork_link(fork) + + return list(resolve_fork_link(self.db_repo)) + @LoginRequired() @HasRepoPermissionAnyDecorator("repository.admin") @CSRFRequired() @@ -125,8 +136,6 @@ class RepoSettingsAdvancedView(RepoAppView): """ _ = self.request.translate - 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: old_data = self.db_repo.get_api_data() @@ -134,14 +143,16 @@ class RepoSettingsAdvancedView(RepoAppView): delete_cache = True self._invalidate_remote_cache(delete=delete_cache) - RepoModel().delete(self.db_repo, forks=fork_action) + delete_res = RepoModel().delete(self.db_repo) - _forks = self.db_repo.forks.count() - if _forks and fork_action: - if fork_action is RepoModel.ForkAction.DETACH: - h.flash(_("Detached %s forks") % _forks, category="success") - elif fork_action is RepoModel.ForkAction.DELETE: - h.flash(_("Deleted %s forks") % _forks, category="success") + fork_action = delete_res.fork_action_result.action + fork_cnt = delete_res.fork_action_result.fork_count + + if fork_action is not None: + if fork_action is ForksAction.DETACH: + h.flash(_("Detached %s forks") % fork_cnt, category="success") + elif fork_action is ForksAction.DELETE: + h.flash(_("Deleted %s forks") % fork_cnt, category="success") repo = audit_logger.RepoWrap(repo_id=None, repo_name=self.db_repo.repo_name) audit_logger.store_web( @@ -196,16 +207,6 @@ 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) diff --git a/rhodecode/i18n/en/LC_MESSAGES/rhodecode.po b/rhodecode/i18n/en/LC_MESSAGES/rhodecode.po index 27cbe8eb..969a8763 100644 --- a/rhodecode/i18n/en/LC_MESSAGES/rhodecode.po +++ b/rhodecode/i18n/en/LC_MESSAGES/rhodecode.po @@ -2583,10 +2583,10 @@ msgstr "" msgid "Confirm to delete this repository" msgstr "" -#: rhodecode/templates/admin/repos/repo_edit.html:310 +#: rhodecode/templates/admin/repos/repo_edit.html:208 #, python-format -msgid "this repository has %s fork" -msgid_plural "this repository has %s forks" +msgid "Following fork will be %s" +msgid_plural "Following forks will be %s" msgstr[0] "" msgstr[1] "" diff --git a/rhodecode/i18n/rhodecode.pot b/rhodecode/i18n/rhodecode.pot index bdcf3c3e..26318954 100644 --- a/rhodecode/i18n/rhodecode.pot +++ b/rhodecode/i18n/rhodecode.pot @@ -7567,7 +7567,7 @@ msgstr "" msgid "Delete this repository" msgstr "" -#: rhodecode/templates/admin/repos/repo_edit_advanced.mako:237 +#: rhodecode/templates/admin/repos/repo_edit_advanced.mako:242 msgid "Warning: You are about to delete the repository. This action cannot be undone!" msgstr "" diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py index ef258d21..b3116434 100644 --- a/rhodecode/lib/utils.py +++ b/rhodecode/lib/utils.py @@ -57,7 +57,6 @@ from rhodecode.model import meta from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup from rhodecode.model.meta import Session - log = logging.getLogger(__name__) REMOVED_REPO_PAT = re.compile(r"rm__\d{8}_\d{6}_\d{6}__.*") @@ -665,7 +664,7 @@ def repo2db_mapper(initial_repo_list, force_hooks_rebuild=False): def repo2db_cleanup(skip_repos=None, skip_groups=None): - from rhodecode.model.repo import RepoModel + from rhodecode.model.repo import RepoModel, ForksAction from rhodecode.model.repo_group import RepoGroupModel sa = meta.Session() @@ -688,7 +687,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=RepoModel.ForkAction.DETACH, fs_remove=False, call_events=False) + RepoModel(sa).delete(db_repo, forks_action=ForksAction.DETACH, fs_remove=False, call_events=False) sa.commit() removed.append(db_repo_name) except Exception: diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py index e63932d5..ef65b9f5 100644 --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -23,8 +23,8 @@ import time import logging import traceback import datetime +from dataclasses import dataclass, field from enum import StrEnum, auto -from typing import Optional from sqlalchemy.orm import aliased from zope.cachedescriptors.property import Lazy as LazyProperty @@ -75,18 +75,31 @@ from rhodecode.model.permission import PermissionModel log = logging.getLogger(__name__) +class ForksAction(StrEnum): + DELETE = auto() + DETACH = auto() + UNKNOWN = auto() + + @classmethod + def _missing_(cls, value): + return cls(cls.UNKNOWN) + + +@dataclass +class ForkActionResult: + fork_count: int = 0 + action: ForksAction = None + + +@dataclass +class DeleteRepoResult: + is_success: bool + fork_action_result: ForkActionResult = field(default_factory=ForkActionResult) + + 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) @@ -762,37 +775,43 @@ class RepoModel(BaseModel): def delete( self, repo: Repository, - forks: ForkAction = None, + forks_action: ForksAction = None, # used only in API to enforce action fs_remove: bool = True, cur_user: User = None, call_events: bool = True, - ) -> bool: + ) -> DeleteRepoResult: """ - Delete given repository, forks parameter defines what do do with - attached forks. Throws AttachedForksError if deleted repo has attached - forks + Delete a given repository. + Throws AttachedForksError if a deleted repo has attached forks :param repo: - :param forks: ForkAction 'delete' or 'detach', NOTE: left for backward compatibility, do not use it + :param forks_action: + NOTE: used only in API to enforce action; + If the repository is private, then forks will be removed. + If the repository is public, then forks will be detached. + :param fs_remove: remove(archive) repo from filesystem """ - if not cur_user: - cur_user = getattr(get_current_rhodecode_user(), "username", None) repo = self._get_repo(repo) if not repo: - return False + return DeleteRepoResult(is_success=False) - if forks is None or forks is self.ForkAction.UNKNOWN: + if not cur_user: + cur_user = getattr(get_current_rhodecode_user(), "username", None) + + if forks_action is None or forks_action is ForksAction.UNKNOWN: is_private = repo.private - forks = self.ForkAction.DELETE if is_private else self.ForkAction.DETACH + forks_action = ForksAction.DELETE if is_private else ForksAction.DETACH - if forks is self.ForkAction.DETACH: + forks_cnt = repo.forks.count() + + if forks_action is ForksAction.DETACH: for r in repo.forks: r.fork = None self.sa.add(r) - elif forks is self.ForkAction.DELETE: + elif forks_action is ForksAction.DELETE: for r in repo.forks: - self.delete(r, forks=self.ForkAction.DELETE) + self.delete(r, forks_action=forks_action) elif repo.forks.first() is not None: raise AttachedForksError() @@ -821,7 +840,9 @@ class RepoModel(BaseModel): log.error(traceback.format_exc()) raise - return True + return DeleteRepoResult( + is_success=True, fork_action_result=ForkActionResult(action=forks_action, fork_count=forks_cnt) + ) def _delete_repo_related_assets(self, repo): pr_sources = repo.pull_requests_source diff --git a/rhodecode/templates/admin/repos/repo_edit_advanced.mako b/rhodecode/templates/admin/repos/repo_edit_advanced.mako index 1ddfca2d..dd920288 100644 --- a/rhodecode/templates/admin/repos/repo_edit_advanced.mako +++ b/rhodecode/templates/admin/repos/repo_edit_advanced.mako @@ -200,21 +200,19 @@
${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), request=request)} - <% forks = c.rhodecode_db_repo.forks.count() %> + <% forks = len(c.fork_links) %> + <% delete_forks = c.rhodecode_db_repo.private %> % if forks: - - % endif @@ -222,8 +220,10 @@ <% attached_prs = len(c.rhodecode_db_repo.pull_requests_source + c.rhodecode_db_repo.pull_requests_target) %> % if attached_prs: - % endif @@ -231,23 +231,11 @@ <% attached_artifacts = len(c.rhodecode_db_repo.artifacts) %> % if attached_artifacts: - % endif - - - -
- ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', forks) % forks} + + ${_ungettext('Following fork will be %s:', 'Following forks will be %s:', forks) % ('deleted' if delete_forks else 'detached')} - %if forks: - - %endif - - %if forks: - - %endif +
- ${_ungettext('This repository has %s attached pull request.', 'This repository has %s attached pull requests.', attached_prs) % attached_prs} + + + ${_ungettext('This repository has %s attached pull request (open/close).', 'This repository has %s attached pull requests (open/close).', attached_prs) % attached_prs} +
+ ${_ungettext('This repository has %s attached artifact.', 'This repository has %s attached artifacts.', attached_artifacts) % attached_artifacts}
- % if attached_artifacts or attached_prs or forks: -
-
- % endif - - ${_('Warning: You are about to delete the repository. This action cannot be undone!')} - -
diff --git a/rhodecode/tests/models/test_repos.py b/rhodecode/tests/models/test_repos.py index 389aaabc..2c387baa 100644 --- a/rhodecode/tests/models/test_repos.py +++ b/rhodecode/tests/models/test_repos.py @@ -26,7 +26,7 @@ from rhodecode.lib.exceptions import AttachedForksError from rhodecode.lib.utils import make_db_config from rhodecode.model.db import Repository from rhodecode.model.meta import Session -from rhodecode.model.repo import RepoModel +from rhodecode.model.repo import RepoModel, ForksAction from rhodecode.model.scm import ScmModel @@ -61,7 +61,7 @@ class TestRepoModel(object): fork_of_fork = backend.create_fork() Session().commit() - RepoModel().delete(repo=repo, forks=RepoModel.ForkAction.DELETE) + RepoModel().delete(repo=repo, forks_action=ForksAction.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=RepoModel.ForkAction.DETACH) + RepoModel().delete(repo=repo, forks_action=ForksAction.DETACH) Session().commit() assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None