API: Creating a Project with a full ref as Default Branch Omits Initialization
Summary
When creating a GitLab project while calling the Gitlab projects API, specifying the default_branch as a full ref (e.g., refs/heads/main) and setting initialize_with_readme to true results in the project being created without an initialized README and without setting the specified default branch.
This behavior was observed downstream in the Gitlab Terraform Provider:
terraform-provider-gitlab#6309
Steps to reproduce
- Create a new project using for example Curl to call the projects API
- Set
default_branch
to a full ref i.e.refs/heads/main
- Set
initialize_with_readme
totrue
- Set
We can see the following behavior:
- Default branch has been changed to main
- Created project is not initialized
Example Project
N/A
What is the current bug behavior?
The project is created unitialized, and the default branch is not set as specified.
What is the expected correct behavior?
Provide a 400 Bad Request
when a full ref is provided as a default branch instead of ignoring the provided default branch name.
Relevant logs and/or screenshots
Call Projects API creating a new project with default branch refs/heads/master
and initialize_with_readme
set to true.
refs/heads/master
and initialize_with_readme
set to true.curl --request POST \
--header "PRIVATE-TOKEN: <PAT>" \
--header "Content-Type: application/json" \
--data '{
"name": "terraform-project",
"path": "terraform-project",
"namespace_id": 2666,
"default_branch": "refs/heads/master",
"initialize_with_readme": true,
"visibility": "private",
"merge_requests_enabled": true,
"forking_access_level": "disabled",
"lfs_enabled": false,
"packages_enabled": false,
"container_registry_access_level": "disabled",
"analytics_access_level": "enabled",
"security_and_compliance_access_level": "enabled",
"wiki_enabled": false,
"snippets_enabled": false,
"issues_enabled": false,
"issues_access_level": "disabled",
"releases_access_level": "enabled",
"environments_access_level": "enabled",
"feature_flags_access_level": "disabled",
"infrastructure_access_level": "private",
"monitor_access_level": "disabled",
"merge_pipelines_enabled": false,
"resolve_outdated_diff_discussions": false,
"printing_merge_request_link_enabled": true,
"remove_source_branch_after_merge": true,
"squash_option": "default_off",
"only_allow_merge_if_pipeline_succeeds": true,
"only_allow_merge_if_all_discussions_are_resolved": true,
"builds_access_level": "enabled",
"public_jobs": false
}' \
"http://<URL>/api/v4/projects" | jq
If we check the results from the API call, we notice the following:
Output
{
"id": 1372,
"description": null,
"name": "terraform-project",
"name_with_namespace": "token-test / terraform-project",
"path": "terraform-project",
"path_with_namespace": "token-test/terraform-project",
"created_at": "2025-01-31T21:30:48.441Z",
"default_branch": "main",
"tag_list": [],
"topics": [],
"ssh_url_to_repo": "ssh://git@<URL>:2222/token-test/terraform-project.git",
"http_url_to_repo": "http://<URL>/token-test/terraform-project.git",
"web_url": "http://<URL>/token-test/terraform-project",
"readme_url": null,
"forks_count": 0,
"avatar_url": null,
"star_count": 0,
"last_activity_at": "2025-01-31T21:30:48.413Z",
"namespace": {
"id": 2666,
"name": "token-test",
"path": "token-test",
"kind": "group",
"full_path": "token-test",
"parent_id": null,
"avatar_url": null,
"web_url": "http://<URL>/groups/token-test"
},
"repository_storage": "default",
"_links": {
"self": "http://<URL>/api/v4/projects/1372",
"merge_requests": "http://<URL>/api/v4/projects/1372/merge_requests",
"repo_branches": "http://<URL>/api/v4/projects/1372/repository/branches",
"labels": "http://<URL>/api/v4/projects/1372/labels",
"events": "http://<URL>/api/v4/projects/1372/events",
"members": "http://<URL>/api/v4/projects/1372/members",
"cluster_agents": "http://<URL>/api/v4/projects/1372/cluster_agents"
},
"packages_enabled": false,
"empty_repo": true,
"archived": false,
"visibility": "private",
"resolve_outdated_diff_discussions": false,
"container_expiration_policy": {
"cadence": "1d",
"enabled": false,
"keep_n": 10,
"older_than": "90d",
"name_regex": ".*",
"name_regex_keep": null,
"next_run_at": "2025-02-01T21:30:48.447Z"
},
"repository_object_format": "sha1",
"issues_enabled": false,
"merge_requests_enabled": true,
"wiki_enabled": false,
"jobs_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": false,
"service_desk_enabled": false,
"can_create_merge_request_in": true,
"issues_access_level": "disabled",
"repository_access_level": "enabled",
"merge_requests_access_level": "enabled",
"forking_access_level": "disabled",
"wiki_access_level": "disabled",
"builds_access_level": "enabled",
"snippets_access_level": "disabled",
"pages_access_level": "private",
"analytics_access_level": "enabled",
"container_registry_access_level": "disabled",
"security_and_compliance_access_level": "enabled",
"releases_access_level": "enabled",
"environments_access_level": "enabled",
"feature_flags_access_level": "disabled",
"infrastructure_access_level": "private",
"monitor_access_level": "disabled",
"model_experiments_access_level": "enabled",
"model_registry_access_level": "enabled",
"emails_disabled": false,
"emails_enabled": true,
"shared_runners_enabled": true,
"lfs_enabled": false,
"creator_id": 1,
"import_url": null,
"import_type": null,
"import_status": "none",
"import_error": null,
"description_html": "",
"updated_at": "2025-01-31T21:30:48.441Z",
"ci_default_git_depth": 20,
"ci_delete_pipelines_in_seconds": null,
"ci_forward_deployment_enabled": true,
"ci_forward_deployment_rollback_allowed": true,
"ci_job_token_scope_enabled": false,
"ci_separated_caches": true,
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
"ci_id_token_sub_claim_components": [
"project_path",
"ref_type",
"ref"
],
"build_git_strategy": "fetch",
"keep_latest_artifact": true,
"restrict_user_defined_variables": true,
"ci_pipeline_variables_minimum_override_role": "developer",
"runners_token": "GR1348941pznzhhsweqKuXBmiXZH8",
"runner_token_expiration_interval": null,
"group_runners_enabled": true,
"auto_cancel_pending_pipelines": "enabled",
"build_timeout": 3600,
"auto_devops_enabled": true,
"auto_devops_deploy_strategy": "continuous",
"ci_push_repository_for_job_token_allowed": false,
"ci_config_path": null,
"public_jobs": false,
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": true,
"allow_merge_on_skipped_pipeline": null,
"request_access_enabled": true,
"only_allow_merge_if_all_discussions_are_resolved": true,
"remove_source_branch_after_merge": true,
"printing_merge_request_link_enabled": true,
"merge_method": "merge",
"squash_option": "default_off",
"enforce_auth_checks_on_uploads": true,
"suggestion_commit_message": null,
"merge_commit_template": null,
"squash_commit_template": null,
"issue_branch_template": null,
"warn_about_potentially_unwanted_characters": true,
"autoclose_referenced_issues": true,
"requirements_enabled": false,
"requirements_access_level": "enabled",
"security_and_compliance_enabled": true,
"compliance_frameworks": []
}
As seen in the output, the default_branch
has automatically been set to main
also notice that the readme_url
is set to null
.
Output of checks
Results of GitLab environment info
N/A
Results of GitLab application Check
N/A
Possible fixes
Investigate whether the GitLab Projects API is intentionally designed to ignore default_branch values that include a full ref. If so, the API should return a 400 Bad Request error when such values are provided, rather than silently ignoring them. Alternatively, the API could be updated to handle default_branch values that include a full ref appropriately