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
This commit is contained in:
2026-05-22 20:02:51 +02:00
parent 4547105f3b
commit 10cbe63095
3 changed files with 25 additions and 4 deletions
+7
View File
@@ -121,6 +121,13 @@ def create_branch(repo_path: str, name: str, base_branch: str = "HEAD") -> None:
Raises: Raises:
RuntimeError: If branch creation fails 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) _run_git_command(repo_path, "branch", name, base_branch)
@@ -4,6 +4,7 @@ import pytest
from fastapi import HTTPException from fastapi import HTTPException
from src.api.git_repositories import _clone_working_repository, _init_working_repository 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: 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[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[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"] 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")
+6 -4
View File
@@ -112,7 +112,7 @@ export const GitToolbar = ({
if (!newBranchName.trim()) return; if (!newBranchName.trim()) return;
setLoading(true); setLoading(true);
try { try {
await createBranch(projectId, repoId, newBranchName, newBranchBase || "HEAD"); await createBranch(projectId, repoId, newBranchName, newBranchBase || currentBranch || "HEAD");
setShowNewBranch(false); setShowNewBranch(false);
setNewBranchName(""); setNewBranchName("");
setNewBranchBase(""); setNewBranchBase("");
@@ -131,6 +131,8 @@ export const GitToolbar = ({
status.untracked.length > 0 status.untracked.length > 0
); );
const canSync = hasRemote;
return ( return (
<div className="git-toolbar"> <div className="git-toolbar">
{error && <div className="toolbar-error">{error}</div>} {error && <div className="toolbar-error">{error}</div>}
@@ -169,7 +171,7 @@ export const GitToolbar = ({
<button <button
className="toolbar-button" className="toolbar-button"
onClick={handleFetch} onClick={handleFetch}
disabled={loading || !hasRemote} disabled={loading || !canSync}
type="button" type="button"
> >
<Icon name="fetch" size="sm" /> Fetch <Icon name="fetch" size="sm" /> Fetch
@@ -177,7 +179,7 @@ export const GitToolbar = ({
<button <button
className="toolbar-button" className="toolbar-button"
onClick={handlePull} onClick={handlePull}
disabled={loading || !hasRemote} disabled={loading || !canSync}
type="button" type="button"
> >
<Icon name="pull" size="sm" /> Pull <Icon name="pull" size="sm" /> Pull
@@ -186,7 +188,7 @@ export const GitToolbar = ({
<button <button
className="toolbar-button" className="toolbar-button"
onClick={handlePush} onClick={handlePush}
disabled={loading || !status?.ahead} disabled={loading || !canSync || !status?.ahead}
type="button" type="button"
> >
<Icon name="push" size="sm" /> Push <Icon name="push" size="sm" /> Push