From 10cbe63095cf552869fc8f10daf8682f421e2bea Mon Sep 17 00:00:00 2001 From: marxlaml Date: Fri, 22 May 2026 20:02:51 +0200 Subject: [PATCH] fix: handle unborn HEAD branch creation - Create orphan branches when HEAD does not exist yet\n- Use current branch as the default branch base in the toolbar\n- Keep push disabled for remote-less repos\n\nQuality gates: vitest repositories-settings-tab (passed); api pytest blocked by missing fastapi in environment --- apps/api/src/utils/git_control.py | 7 +++++++ .../tests/unit/test_git_repository_working_clones.py | 12 ++++++++++++ apps/web/src/components/git-toolbar.tsx | 10 ++++++---- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/apps/api/src/utils/git_control.py b/apps/api/src/utils/git_control.py index a5ea7ea..59a6874 100644 --- a/apps/api/src/utils/git_control.py +++ b/apps/api/src/utils/git_control.py @@ -121,6 +121,13 @@ def create_branch(repo_path: str, name: str, base_branch: str = "HEAD") -> None: Raises: RuntimeError: If branch creation fails """ + if base_branch == "HEAD": + try: + _run_git_command(repo_path, "rev-parse", "--verify", "HEAD") + except RuntimeError: + _run_git_command(repo_path, "checkout", "--orphan", name) + return + _run_git_command(repo_path, "branch", name, base_branch) diff --git a/apps/api/tests/unit/test_git_repository_working_clones.py b/apps/api/tests/unit/test_git_repository_working_clones.py index de93166..1fdd2cb 100644 --- a/apps/api/tests/unit/test_git_repository_working_clones.py +++ b/apps/api/tests/unit/test_git_repository_working_clones.py @@ -4,6 +4,7 @@ import pytest from fastapi import HTTPException from src.api.git_repositories import _clone_working_repository, _init_working_repository +from src.utils.git_control import create_branch def test_clone_working_repository_uses_normal_clone() -> None: @@ -44,3 +45,14 @@ def test_init_working_repository_falls_back_to_symbolic_ref() -> None: assert run_mock.call_args_list[0].args[0] == ["git", "init", "-b", "main", "/tmp/new-repo"] assert run_mock.call_args_list[1].args[0] == ["git", "init", "/tmp/new-repo"] assert run_mock.call_args_list[2].args[0] == ["git", "-C", "/tmp/new-repo", "symbolic-ref", "HEAD", "refs/heads/main"] + + +def test_create_branch_uses_orphan_checkout_when_head_is_unborn() -> None: + verify_head = Mock(returncode=128, stderr="fatal: Needed a single revision") + checkout_orphan = Mock(returncode=0, stderr="") + + with patch("src.utils.git_control._run_git_command", side_effect=[verify_head, checkout_orphan]) as run_mock: + create_branch("/tmp/new-repo", "feature/test") + + assert run_mock.call_args_list[0].args[1:] == ("rev-parse", "--verify", "HEAD") + assert run_mock.call_args_list[1].args[1:] == ("checkout", "--orphan", "feature/test") diff --git a/apps/web/src/components/git-toolbar.tsx b/apps/web/src/components/git-toolbar.tsx index 9ef5f39..31a8f07 100644 --- a/apps/web/src/components/git-toolbar.tsx +++ b/apps/web/src/components/git-toolbar.tsx @@ -112,7 +112,7 @@ export const GitToolbar = ({ if (!newBranchName.trim()) return; setLoading(true); try { - await createBranch(projectId, repoId, newBranchName, newBranchBase || "HEAD"); + await createBranch(projectId, repoId, newBranchName, newBranchBase || currentBranch || "HEAD"); setShowNewBranch(false); setNewBranchName(""); setNewBranchBase(""); @@ -131,6 +131,8 @@ export const GitToolbar = ({ status.untracked.length > 0 ); + const canSync = hasRemote; + return (
{error &&
{error}
} @@ -169,7 +171,7 @@ export const GitToolbar = ({