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:
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 (
|
||||
<div className="git-toolbar">
|
||||
{error && <div className="toolbar-error">{error}</div>}
|
||||
@@ -169,7 +171,7 @@ export const GitToolbar = ({
|
||||
<button
|
||||
className="toolbar-button"
|
||||
onClick={handleFetch}
|
||||
disabled={loading || !hasRemote}
|
||||
disabled={loading || !canSync}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="fetch" size="sm" /> Fetch
|
||||
@@ -177,7 +179,7 @@ export const GitToolbar = ({
|
||||
<button
|
||||
className="toolbar-button"
|
||||
onClick={handlePull}
|
||||
disabled={loading || !hasRemote}
|
||||
disabled={loading || !canSync}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="pull" size="sm" /> Pull
|
||||
@@ -186,7 +188,7 @@ export const GitToolbar = ({
|
||||
<button
|
||||
className="toolbar-button"
|
||||
onClick={handlePush}
|
||||
disabled={loading || !status?.ahead}
|
||||
disabled={loading || !canSync || !status?.ahead}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="push" size="sm" /> Push
|
||||
|
||||
Reference in New Issue
Block a user