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:
|
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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user