diff --git a/apps/api/src/utils/git_control.py b/apps/api/src/utils/git_control.py index becebc1..92e57ba 100644 --- a/apps/api/src/utils/git_control.py +++ b/apps/api/src/utils/git_control.py @@ -124,7 +124,15 @@ def create_branch(repo_path: str, name: str, base_branch: str = "HEAD") -> None: try: _run_git_command(repo_path, "rev-parse", "--verify", "HEAD^{commit}") except RuntimeError: - _run_git_command(repo_path, "checkout", "--orphan", name) + # No commits yet - empty repository + try: + _run_git_command(repo_path, "checkout", "--orphan", name) + except RuntimeError as e: + if "work tree" in str(e).lower(): + # Bare repository - use symbolic-ref instead + _run_git_command(repo_path, "symbolic-ref", "HEAD", f"refs/heads/{name}") + return + raise return _run_git_command(repo_path, "branch", name, base_branch) @@ -155,7 +163,14 @@ def checkout_branch(repo_path: str, name: str) -> None: Raises: RuntimeError: If checkout fails """ - _run_git_command(repo_path, "checkout", name) + try: + _run_git_command(repo_path, "checkout", name) + except RuntimeError as e: + if "work tree" in str(e).lower(): + # Bare repository - use symbolic-ref instead + _run_git_command(repo_path, "symbolic-ref", "HEAD", f"refs/heads/{name}") + return + raise def commit_changes( @@ -290,6 +305,10 @@ def get_current_branch(repo_path: str) -> str: Current branch name """ try: - return _run_git_command(repo_path, "rev-parse", "--abbrev-ref", "HEAD").strip() + branch = _run_git_command(repo_path, "rev-parse", "--abbrev-ref", "HEAD").strip() + if branch != "HEAD": + return branch except RuntimeError: - return _run_git_command(repo_path, "symbolic-ref", "--short", "HEAD").strip() + pass + + return _run_git_command(repo_path, "symbolic-ref", "--short", "HEAD").strip() diff --git a/apps/api/tests/integration/test_git_control.py b/apps/api/tests/integration/test_git_control.py index a7ab139..37d5425 100644 --- a/apps/api/tests/integration/test_git_control.py +++ b/apps/api/tests/integration/test_git_control.py @@ -70,6 +70,20 @@ def test_get_current_branch_handles_unborn_main() -> None: assert get_current_branch(tmpdir) == "main" +def test_create_branch_on_bare_repo_with_no_commits() -> None: + with tempfile.TemporaryDirectory() as tmpdir: + os.system(f"git init --bare {tmpdir}/bare.git >/dev/null 2>&1") + create_branch(f"{tmpdir}/bare.git", "main") + assert get_current_branch(f"{tmpdir}/bare.git") == "main" + + +def test_checkout_branch_on_bare_repo_with_no_commits() -> None: + with tempfile.TemporaryDirectory() as tmpdir: + os.system(f"git init --bare {tmpdir}/bare.git >/dev/null 2>&1") + checkout_branch(f"{tmpdir}/bare.git", "main") + assert get_current_branch(f"{tmpdir}/bare.git") == "main" + + class TestBranchOperations: """Tests for branch management functions.""" diff --git a/apps/web/src/components/app-shell.tsx b/apps/web/src/components/app-shell.tsx index fd2a02f..8636e49 100644 --- a/apps/web/src/components/app-shell.tsx +++ b/apps/web/src/components/app-shell.tsx @@ -9,8 +9,9 @@ import { useSessions } from "../state/sessions"; import { Icon } from "./icon"; import type { IconName } from "../utils/icons"; -const NAV_ITEMS: { to: string; label: string; icon: IconName }[] = [ +const NAV_ITEMS: { to: string; label: string; icon: IconName; badge?: "sessions" }[] = [ { to: "/", label: "Home", icon: "dashboard" }, + { to: "/sessions", label: "Sessions", icon: "terminal", badge: "sessions" }, { to: "/projects", label: "Projects", icon: "projects" }, { to: "/tool-workshop", label: "Tool Workshop", icon: "settings" }, { to: "/settings", label: "Settings", icon: "settings" } @@ -83,7 +84,6 @@ export const AppShell = () => {