Merge pull request !2784 from rhodecode-enterprise-ce feature/RCCE-136_ghost-pull-requests-prevents-repo-from-deleting
Changes from branch: Feature/RCCE 136 ghost pull requests prevents repo from deleting
This commit is contained in:
commit
74a02ee5fd
15 changed files with 243 additions and 95 deletions
36
docs/release-notes/release-notes-5.7.0.rst
Normal file
36
docs/release-notes/release-notes-5.7.0.rst
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
|RCE| 5.7.0 |RNS|
|
||||
-----------------
|
||||
|
||||
Release Date
|
||||
^^^^^^^^^^^^
|
||||
|
||||
- 2025-08-15
|
||||
|
||||
New Features
|
||||
^^^^^^^^^^^^
|
||||
- squash: The pull request UI now includes a dropdown menu, allowing users to select "Squash" or "Close" when merging pull requests in Git and Mercurial repositories, instead of only having the "Merge" option. This feature is exclusive to the Enterprise Edition (EE) and must be enabled by an administrator.
|
||||
- pull request: Introduced a new checkbox in the pull request UI, allowing users to select the option to close the branch after merging. This feature is exclusive to the Enterprise Edition (EE).
|
||||
- user: It is now possible to remove users from the system. When a user is deleted, all of their assets will automatically be reassigned to a new system user, ‘Ghost User’.
|
||||
- active directory: Added the ability to sync active and inactive users from LDAP/Active Directory. This feature must be enabled in the LDAP authentication plugin by an administrator, is available only for Active Directory, and is exclusive to the Enterprise Edition (EE).
|
||||
|
||||
General
|
||||
^^^^^^^
|
||||
|
||||
|
||||
Security
|
||||
^^^^^^^^
|
||||
|
||||
|
||||
|
||||
Performance
|
||||
^^^^^^^^^^^
|
||||
|
||||
|
||||
|
||||
Fixes
|
||||
^^^^^
|
||||
|
||||
- ssh: Fixed an issue where the app.service_api.token field could be left empty in the configuration. The application will now automatically populate this field if it is missing.
|
||||
|
||||
Upgrade notes
|
||||
^^^^^^^^^^^^^
|
||||
|
|
@ -19,6 +19,8 @@
|
|||
import mock
|
||||
import pytest
|
||||
|
||||
from rhodecode.model.db import Repository
|
||||
from rhodecode.model.meta import Session
|
||||
from rhodecode.model.repo import RepoModel
|
||||
from rhodecode.api.tests.utils import build_data, api_call, assert_error, assert_ok, crash
|
||||
|
||||
|
|
@ -27,6 +29,9 @@ from rhodecode.api.tests.utils import build_data, api_call, assert_error, assert
|
|||
class TestApiDeleteRepo(object):
|
||||
def test_api_delete_repo(self, backend):
|
||||
repo = backend.create_repo()
|
||||
repo.private = True
|
||||
Session().commit()
|
||||
|
||||
repo_name = repo.repo_name
|
||||
id_, params = build_data(
|
||||
self.apikey,
|
||||
|
|
@ -38,6 +43,46 @@ class TestApiDeleteRepo(object):
|
|||
expected = {"msg": "Deleted repository `%s`" % (repo_name,), "success": True}
|
||||
assert_ok(id_, expected, given=response.body)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fork_action, expected_fork_msg",
|
||||
[
|
||||
("detach", "Detached 1 forks"),
|
||||
("delete", "Deleted 1 forks"),
|
||||
],
|
||||
)
|
||||
def test_api_delete_repo_with_forks(self, backend, fork_action, expected_fork_msg):
|
||||
repo = backend.create_repo()
|
||||
repo.private = True
|
||||
fork = backend.create_fork()
|
||||
fork_name = fork.repo_name
|
||||
Session().commit()
|
||||
|
||||
assert fork.parent is not None
|
||||
|
||||
repo_name = repo.repo_name
|
||||
id_, params = build_data(
|
||||
self.apikey,
|
||||
"delete_repo",
|
||||
repoid=repo.repo_name,
|
||||
forks=fork_action,
|
||||
)
|
||||
response = api_call(self.app, params)
|
||||
|
||||
expected = {
|
||||
"msg": "Deleted repository `%s` %s; NOTE: 'forks' option will be deprecated."
|
||||
% (repo_name, expected_fork_msg),
|
||||
"success": True,
|
||||
}
|
||||
assert_ok(id_, expected, given=response.body)
|
||||
self._validate_fork_db_state(fork_action, fork_name)
|
||||
|
||||
def _validate_fork_db_state(self, fork_action, fork_name):
|
||||
fork = Repository.get_by_repo_name(repo_name=fork_name)
|
||||
if fork_action == "detach":
|
||||
assert fork.parent is None
|
||||
elif fork_action == "delete":
|
||||
assert fork is None
|
||||
|
||||
def test_api_delete_repo_by_non_admin(self, backend, user_regular):
|
||||
repo = backend.create_repo(cur_user=user_regular.username)
|
||||
repo_name = repo.repo_name
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ def assert_ok(id_, expected, given):
|
|||
|
||||
expected = jsonify({"id": id_, "error": None, "result": expected})
|
||||
|
||||
assert expected == given
|
||||
assert expected == given, "Expected: %s, Given: %s" % (expected, given)
|
||||
|
||||
|
||||
def assert_error(id_, expected, given):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1360,17 +1360,18 @@ def delete_repo(request, apiuser, repoid, forks=Optional("")):
|
|||
validate_repo_permissions(apiuser, repoid, repo, _perms)
|
||||
|
||||
try:
|
||||
handle_forks = Optional.extract(forks)
|
||||
str_fork_action = Optional.extract(forks)
|
||||
_forks_msg = ""
|
||||
_forks = [f for f in repo.forks]
|
||||
if handle_forks == "detach":
|
||||
if str_fork_action == "detach":
|
||||
_forks_msg = " " + f"Detached {len(_forks)} forks"
|
||||
elif handle_forks == "delete":
|
||||
elif str_fork_action == "delete":
|
||||
_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_action=ForksAction(str_fork_action))
|
||||
|
||||
repo = audit_logger.RepoWrap(repo_id=None, repo_name=repo.repo_name)
|
||||
|
||||
|
|
@ -1378,7 +1379,12 @@ def delete_repo(request, apiuser, repoid, forks=Optional("")):
|
|||
|
||||
ScmModel().mark_for_invalidation(repo_name, delete=True)
|
||||
Session().commit()
|
||||
return {"msg": f"Deleted repository `{repo_name}`{_forks_msg}", "success": True}
|
||||
res_message = {"msg": f"Deleted repository `{repo_name}`{_forks_msg}", "success": True}
|
||||
if str_fork_action:
|
||||
deprecation_warning = "; NOTE: 'forks' option will be deprecated."
|
||||
res_message["msg"] += deprecation_warning
|
||||
|
||||
return res_message
|
||||
except Exception:
|
||||
log.exception("Exception occurred while trying to delete repo")
|
||||
raise JSONRPCError(f"failed to delete repository `{repo_name}`")
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ from unittest.mock import patch, MagicMock
|
|||
from rhodecode.apps.repository.views.repo_settings_advanced import RepoSettingsAdvancedView
|
||||
from rhodecode.lib.str_utils import safe_str
|
||||
from rhodecode.model.db import Repository
|
||||
from rhodecode.model.repo import RepoModel
|
||||
from rhodecode.model.meta import Session
|
||||
from rhodecode.model.repo import RepoModel, ForksAction
|
||||
from rhodecode.tests import HG_REPO, GIT_REPO, assert_session_flash, no_newline_id_generator
|
||||
from rhodecode.tests.fixtures.rc_fixture import Fixture
|
||||
from rhodecode.tests.utils import repo_on_filesystem
|
||||
|
|
@ -61,7 +62,7 @@ class TestAdminRepoSettingsAdvanced(object):
|
|||
|
||||
response.mustcontain(opt)
|
||||
|
||||
fixture.destroy_repo(target_repo, forks="detach")
|
||||
fixture.destroy_repo(target_repo, forks_action=ForksAction.DETACH)
|
||||
|
||||
@pytest.mark.backends("hg", "git")
|
||||
def test_set_fork_of_other_type_repo(self, autologin_user, backend, csrf_token):
|
||||
|
|
@ -97,7 +98,10 @@ class TestAdminRepoSettingsAdvanced(object):
|
|||
|
||||
@pytest.mark.parametrize("suffix", ["", "ąęł", "123"], ids=no_newline_id_generator)
|
||||
def test_advanced_repo_delete(self, autologin_user, backend, suffix, csrf_token):
|
||||
repo = backend.create_repo(name_suffix=suffix)
|
||||
repo = backend.create_repo(name_suffix=suffix, private_repo=True)
|
||||
repo.private = True
|
||||
Session().commit()
|
||||
|
||||
repo_name = repo.repo_name
|
||||
repo_name_str = safe_str(repo.repo_name)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -124,11 +135,6 @@ 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"
|
||||
|
||||
repo_advanced_url = h.route_path("edit_repo_advanced", repo_name=self.db_repo_name, _anchor="advanced-delete")
|
||||
try:
|
||||
|
|
@ -137,14 +143,16 @@ class RepoSettingsAdvancedView(RepoAppView):
|
|||
delete_cache = True
|
||||
self._invalidate_remote_cache(delete=delete_cache)
|
||||
|
||||
RepoModel().delete(self.db_repo, forks=handle_forks)
|
||||
delete_res = RepoModel().delete(self.db_repo)
|
||||
|
||||
_forks = self.db_repo.forks.count()
|
||||
if _forks and handle_forks:
|
||||
if handle_forks == "detach_forks":
|
||||
h.flash(_("Detached %s forks") % _forks, category="success")
|
||||
elif handle_forks == "delete_forks":
|
||||
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_cnt > 0 and 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(
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ def initialize_ini_config_default_values_if_not_present(ini_path: str):
|
|||
with open(ini_path, "w") as configfile:
|
||||
updater.write(configfile)
|
||||
|
||||
if not ini_path or not os.path.exists(ini_path):
|
||||
log.warning("Config file %s not found.", ini_path)
|
||||
return
|
||||
|
||||
updater = ConfigUpdater()
|
||||
updater.read(ini_path)
|
||||
|
||||
|
|
|
|||
|
|
@ -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] ""
|
||||
|
||||
|
|
|
|||
|
|
@ -7567,7 +7567,7 @@ msgstr ""
|
|||
msgid "Delete this repository"
|
||||
msgstr ""
|
||||
|
||||
#: rhodecode/templates/admin/repos/repo_edit_advanced.mako:242
|
||||
#: rhodecode/templates/admin/repos/repo_edit_advanced.mako:249
|
||||
msgid "This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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="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:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
|
@ -23,6 +23,8 @@ import time
|
|||
import logging
|
||||
import traceback
|
||||
import datetime
|
||||
from dataclasses import dataclass, field
|
||||
from enum import StrEnum, auto
|
||||
|
||||
from sqlalchemy.orm import aliased
|
||||
from zope.cachedescriptors.property import Lazy as LazyProperty
|
||||
|
|
@ -69,11 +71,32 @@ from rhodecode.model.db import (
|
|||
UserLog,
|
||||
)
|
||||
from rhodecode.model.permission import PermissionModel
|
||||
from rhodecode.model.settings import VcsSettingsModel
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -750,47 +773,49 @@ 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_action: ForksAction = None, # used only in API to enforce action
|
||||
fs_remove: bool = True,
|
||||
cur_user: User = None,
|
||||
call_events: bool = True,
|
||||
) -> 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: str 'delete' or 'detach'
|
||||
:param pull_requests: str 'delete' or None
|
||||
:param artifacts: str 'delete' or None
|
||||
: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 == "detach":
|
||||
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_action = ForksAction.DELETE if is_private else ForksAction.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 == "delete":
|
||||
elif forks_action is ForksAction.DELETE:
|
||||
for r in repo.forks:
|
||||
self.delete(r, forks="delete")
|
||||
elif [f for f in repo.forks]:
|
||||
self.delete(r, forks_action=forks_action)
|
||||
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
|
||||
if pull_requests != "delete" and (pr_sources or pr_targets):
|
||||
raise AttachedPullRequestsError()
|
||||
|
||||
artifacts_objs = repo.artifacts
|
||||
if artifacts == "delete":
|
||||
for a in artifacts_objs:
|
||||
self.sa.delete(a)
|
||||
elif [a for a in artifacts_objs]:
|
||||
raise AttachedArtifactsError()
|
||||
self._delete_repo_related_assets(repo)
|
||||
|
||||
old_repo_dict = repo.get_dict()
|
||||
if call_events:
|
||||
|
|
@ -815,7 +840,17 @@ 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
|
||||
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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -200,50 +200,42 @@
|
|||
<div class="panel-body">
|
||||
${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), request=request)}
|
||||
<table class="display">
|
||||
<% forks = len(c.fork_links) %>
|
||||
<% delete_forks = c.rhodecode_db_repo.private %>
|
||||
% if forks:
|
||||
<tr>
|
||||
<td>
|
||||
${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.rhodecode_db_repo.forks.count()) % c.rhodecode_db_repo.forks.count()}
|
||||
<td class="alert-${'error' if delete_forks else 'warning'}" style="padding: 10px">
|
||||
${_ungettext('Following fork will be %s:', 'Following forks will be %s:', forks) % ('deleted' if delete_forks else 'detached')}
|
||||
</td>
|
||||
<td>
|
||||
%if c.rhodecode_db_repo.forks.count():
|
||||
<input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
|
||||
%endif
|
||||
</td>
|
||||
<td>
|
||||
%if c.rhodecode_db_repo.forks.count():
|
||||
<input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label>
|
||||
%endif
|
||||
<ul>
|
||||
% for fork in c.fork_links:
|
||||
<li class="list-unstyled"><a href="${fork}" target="_blank" rel="noopener noreferrer">${fork.replace("/", "")} <span aria-hidden="true" style="font-size: 0.9em;">↗️</span></a></li>
|
||||
% endfor
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
% endif
|
||||
|
||||
<% attached_prs = len(c.rhodecode_db_repo.pull_requests_source + c.rhodecode_db_repo.pull_requests_target) %>
|
||||
% if c.rhodecode_db_repo.pull_requests_source or c.rhodecode_db_repo.pull_requests_target:
|
||||
% if attached_prs:
|
||||
<tr>
|
||||
<td>
|
||||
${_ungettext('This repository has %s attached pull request.', 'This repository has %s attached pull requests.', attached_prs) % attached_prs}
|
||||
<br/>
|
||||
<br/>
|
||||
<strong>${_('Consider to archive this repository instead.')}</strong>
|
||||
<td colspan="2" style="padding: 10px">
|
||||
<a href="${c.pr_link}" target="_blank" rel="noopener noreferrer">
|
||||
${_ungettext('This repository has %s attached pull request (open/close).', 'This repository has %s attached pull requests (open/close).', attached_prs) % attached_prs} <span aria-hidden="true" style="font-size: 0.9em;">↗️</span>
|
||||
</a>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
% endif
|
||||
|
||||
<% attached_artifacts = len(c.rhodecode_db_repo.artifacts) %>
|
||||
% if attached_artifacts:
|
||||
<tr>
|
||||
<td>
|
||||
<td colspan="2">
|
||||
${_ungettext('This repository has %s attached artifact.', 'This repository has %s attached artifacts.', attached_artifacts) % attached_artifacts}
|
||||
<br/>
|
||||
<br/>
|
||||
<strong>${_('Consider to archive this repository instead.')}</strong>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
% endif
|
||||
|
||||
</table>
|
||||
<div style="margin: 0 0 20px 0" class="fake-space"></div>
|
||||
|
||||
|
|
@ -259,7 +251,6 @@
|
|||
${_('This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools.')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
${h.end_form()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
2
rhodecode/tests/fixtures/rc_fixture.py
vendored
2
rhodecode/tests/fixtures/rc_fixture.py
vendored
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ def test_repo2db_mapper_adds_new_repos(request, backend):
|
|||
for _repo in cleanup_repos:
|
||||
del_result = RepoModel().delete(_repo, call_events=False)
|
||||
Session().commit()
|
||||
assert del_result is True
|
||||
assert del_result.is_success is True
|
||||
|
||||
for _repo_group in cleanup_groups:
|
||||
del_result = RepoGroupModel().delete(_repo_group, force_delete=True, call_events=False)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -42,14 +42,34 @@ class TestRepoModel(object):
|
|||
assert Repository.get_by_repo_name(repo_name=backend.repo_name) is None
|
||||
assert repo.repo_name not in repos
|
||||
|
||||
def test_remove_repo_raises_exc_when_attached_forks(self, backend):
|
||||
def test_remove_repo_forks_are_detached(self, backend):
|
||||
repo = backend.create_repo()
|
||||
Session().commit()
|
||||
backend.create_fork()
|
||||
fork = backend.create_fork()
|
||||
fork_name = fork.repo_name
|
||||
Session().commit()
|
||||
|
||||
with pytest.raises(AttachedForksError):
|
||||
RepoModel().delete(repo=repo)
|
||||
assert fork.parent is not None
|
||||
|
||||
RepoModel().delete(repo=repo)
|
||||
|
||||
fork = Repository.get_by_repo_name(repo_name=fork_name)
|
||||
assert fork.parent is None
|
||||
|
||||
def test_remove_repo_forks_are_deleted(self, backend):
|
||||
repo = backend.create_repo()
|
||||
repo.private = True
|
||||
Session().commit()
|
||||
fork = backend.create_fork()
|
||||
fork_name = fork.repo_name
|
||||
Session().commit()
|
||||
|
||||
assert fork.parent is not None
|
||||
|
||||
RepoModel().delete(repo=repo)
|
||||
|
||||
fork = Repository.get_by_repo_name(repo_name=fork_name)
|
||||
assert fork is None
|
||||
|
||||
def test_remove_repo_delete_forks(self, backend):
|
||||
repo = backend.create_repo()
|
||||
|
|
@ -61,7 +81,7 @@ class TestRepoModel(object):
|
|||
fork_of_fork = backend.create_fork()
|
||||
Session().commit()
|
||||
|
||||
RepoModel().delete(repo=repo, forks="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 +98,7 @@ class TestRepoModel(object):
|
|||
fork_of_fork = backend.create_fork()
|
||||
Session().commit()
|
||||
|
||||
RepoModel().delete(repo=repo, forks="detach")
|
||||
RepoModel().delete(repo=repo, forks_action=ForksAction.DETACH)
|
||||
Session().commit()
|
||||
|
||||
assert Repository.get_by_repo_name(repo_name=repo.repo_name) is None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue