feat(repo-creation): added svn bootstrap, and improved repo creation validation

Also added refined UI, ability to pre-validate repo names

fold: repo create

fold repo creation

foldz

feat(repo-creation): added svn bootstrap, and improved repo creation validation

Also added refined UI, ability to pre-validate repo names

fold: repo create

fold repo creation

foldz

fold
This commit is contained in:
RhodeCode Admin 2025-12-23 21:04:15 +01:00
parent ee52770fff
commit cd01bbbddd
23 changed files with 656 additions and 200 deletions

View file

@ -319,7 +319,7 @@ class TestCreateRepo(object):
assert repo is not None
expected = {
"msg": "Created new repository `%s`" % (expected_name,),
"msg": f"Created new repository `{expected_name}`",
"success": True,
"task": None,
}
@ -327,3 +327,49 @@ class TestCreateRepo(object):
fixture.destroy_repo(expected_name)
if parent_group:
fixture.destroy_repo_group(parent_group)
@pytest.mark.parametrize(
"given, expected_exc",
[
("robots.txt", True),
("favicon.ico", True),
],
)
def test_api_create_repo_forbidden_names(self, backend, given, expected_exc):
repo_name = given
id_, params = build_data(
self.apikey,
"create_repo",
repo_name=repo_name,
owner=TEST_USER_ADMIN_LOGIN,
repo_type=backend.alias,
)
response = api_call(self.app, params)
expected = {"repo_name": f"Repository with name `{given}` is forbidden"}
assert_error(id_, expected, given=response.body)
@pytest.mark.parametrize(
"given, expected_exc",
[
("_admin", True),
("_static", True),
("_file_store", True),
("_repos", True),
("_goto_data", True),
("_file_preview", True),
],
)
def test_api_create_repo_forbidden_names_and_wrong_name(self, backend, given, expected_exc):
repo_name = given
id_, params = build_data(
self.apikey,
"create_repo",
repo_name=repo_name,
owner=TEST_USER_ADMIN_LOGIN,
repo_type=backend.alias,
)
response = api_call(self.app, params)
expected = {
"repo_name": f"Name must start with a letter or number. Got `{given}`; Repository with name `{given}` is forbidden"
}
assert_error(id_, expected, given=response.body)

View file

@ -48,7 +48,7 @@ class TestCreateRepoGroup(object):
repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
assert repo_group is not None
ret = {"msg": "Created new repo group `%s`" % (repo_group_name,), "repo_group": repo_group.get_api_data()}
ret = {"msg": f"Created new repo group `{repo_group_name}`", "repo_group": repo_group.get_api_data()}
expected = ret
try:
assert_ok(id_, expected, given=response.body)
@ -75,7 +75,7 @@ class TestCreateRepoGroup(object):
repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
assert repo_group is not None
ret = {"msg": "Created new repo group `%s`" % (full_repo_group_name,), "repo_group": repo_group.get_api_data()}
ret = {"msg": f"Created new repo group `{full_repo_group_name}`", "repo_group": repo_group.get_api_data()}
expected = ret
try:
assert_ok(id_, expected, given=response.body)
@ -115,7 +115,7 @@ class TestCreateRepoGroup(object):
owner=TEST_USER_ADMIN_LOGIN,
)
response = api_call(self.app, params)
expected = {"unique_repo_group_name": "Repository group with name `{}` already exists".format(repo_group_name)}
expected = {"unique_repo_group_name": f"Repository group with name `{repo_group_name}` already exists"}
try:
assert_error(id_, expected, given=response.body)
finally:
@ -142,7 +142,7 @@ class TestCreateRepoGroup(object):
repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
assert repo_group is not None
expected = {"msg": "Created new repo group `%s`" % (repo_group_name,), "repo_group": repo_group.get_api_data()}
expected = {"msg": f"Created new repo group `{repo_group_name}`", "repo_group": repo_group.get_api_data()}
try:
assert_ok(id_, expected, given=response.body)
finally:
@ -174,7 +174,7 @@ class TestCreateRepoGroup(object):
repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
assert repo_group is not None
expected = {
"msg": "Created new repo group `{}`".format(full_repo_group_name),
"msg": f"Created new repo group `{full_repo_group_name}`",
"repo_group": repo_group.get_api_data(),
}
try:
@ -206,8 +206,7 @@ class TestCreateRepoGroup(object):
response = api_call(self.app, params)
expected = {
"repo_group": "You do not have the permissions to store "
"repository groups inside repository group `{}`".format(repo_group_name)
"repo_group": f"You do not have the permissions to store repository groups inside repository group `{repo_group_name}`"
}
try:
assert_error(id_, expected, given=response.body)
@ -242,13 +241,57 @@ class TestCreateRepoGroup(object):
owner=TEST_USER_ADMIN_LOGIN,
)
response = api_call(self.app, params)
expected = "failed to create repo group `%s`" % (repo_group_name,)
expected = f"failed to create repo group `{repo_group_name}`"
assert_error(id_, expected, given=response.body)
@pytest.mark.parametrize(
"given, expected_exc",
[
("_admin", False),
("_static", False),
("_file_store", False),
("_repos", False),
("_goto_data", False),
("_file_preview", False),
],
)
def test_api_create_repo_group_forbidden_and_wrong_names(self, backend, given, expected_exc):
repo_group_name = given
id_, params = build_data(
self.apikey,
"create_repo_group",
group_name=repo_group_name,
owner=TEST_USER_ADMIN_LOGIN,
)
response = api_call(self.app, params)
expected = {
"repo_group_name": f"Name must start with a letter or number. Got `{repo_group_name}`; Repository group with name `{repo_group_name}` is forbidden"
}
assert_error(id_, expected, given=response.body)
@pytest.mark.parametrize(
"given, expected_exc",
[
("favicon.ico", False),
("robots.txt", False),
],
)
def test_api_create_repo_group_forbidden_names(self, backend, given, expected_exc):
repo_group_name = given
id_, params = build_data(
self.apikey,
"create_repo_group",
group_name=repo_group_name,
owner=TEST_USER_ADMIN_LOGIN,
)
response = api_call(self.app, params)
expected = {"repo_group_name": f"Repository group with name `{repo_group_name}` is forbidden"}
assert_error(id_, expected, given=response.body)
def test_create_group_with_extra_slashes_in_name(self, user_util):
existing_repo_group = user_util.create_repo_group()
dirty_group_name = "//{}//group2//".format(existing_repo_group.group_name)
cleaned_group_name = "{}/group2".format(existing_repo_group.group_name)
dirty_group_name = f"//{existing_repo_group.group_name}//group2//"
cleaned_group_name = f"{existing_repo_group.group_name}/group2"
id_, params = build_data(
self.apikey,
@ -259,7 +302,7 @@ class TestCreateRepoGroup(object):
response = api_call(self.app, params)
repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
expected = {
"msg": "Created new repo group `%s`" % (cleaned_group_name,),
"msg": f"Created new repo group `{cleaned_group_name}`",
"repo_group": repo_group.get_api_data(),
}
assert_ok(id_, expected, given=response.body)

View file

@ -777,6 +777,8 @@ def create_repo(
enable_locking=Optional(False),
enable_downloads=Optional(False),
copy_permissions=Optional(False),
bootstrap_readme=Optional(False),
bootstrap_svn_branch_struct=Optional(False),
):
"""
Creates a repository.
@ -816,10 +818,12 @@ def create_repo(
:type enable_downloads: bool
:param enable_statistics:
:type enable_statistics: bool
:param copy_permissions: Copy permission from group in which the
repository is being created.
:param copy_permissions: Copy permission from group in which the repository is being created.
:type copy_permissions: bool
:param bootstrap_readme: Add default readme for new repositories
:type bootstrap_readme: bool
:param bootstrap_svn_branch_struct: Add branches/tags structure for SVN repositories.
:type bootstrap_svn_branch_struct: bool
Example output:
@ -840,7 +844,7 @@ def create_repo(
id : <id_given_in_input>
result : null
error : {
error : {
'failed to create repository `<repo_name>`'
}
@ -865,6 +869,11 @@ def create_repo(
if isinstance(enable_downloads, Optional):
enable_downloads = defs.get("repo_enable_downloads")
if isinstance(bootstrap_readme, Optional):
bootstrap_readme = Optional.extract(bootstrap_readme)
if isinstance(bootstrap_svn_branch_struct, Optional):
bootstrap_svn_branch_struct = Optional.extract(bootstrap_svn_branch_struct)
landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
ref_choices, _labels = ScmModel().get_repo_landing_revs(request.translate)
ref_choices = list(set(ref_choices + [landing_ref]))
@ -894,6 +903,8 @@ def create_repo(
repo_enable_statistics=enable_statistics,
repo_enable_downloads=enable_downloads,
repo_enable_locking=enable_locking,
repo_bootstrap_readme=bootstrap_readme,
repo_bootstrap_svn_branch_struct=bootstrap_svn_branch_struct,
)
)
except validation_schema.Invalid as err:
@ -915,6 +926,8 @@ def create_repo(
"enable_locking": schema_data["repo_enable_locking"],
"enable_downloads": schema_data["repo_enable_downloads"],
"repo_copy_permissions": schema_data["repo_copy_permissions"],
"repo_bootstrap_readme": schema_data["repo_bootstrap_readme"],
"repo_bootstrap_svn_branch_struct": schema_data["repo_bootstrap_svn_branch_struct"],
}
task = RepoModel().create(form_data=data, cur_user=owner.user_id)