Beads Git Integration: How It Uses Git as Primary Storage
Beads treats Git repositories as its primary storage layer through a work-tree-aware helper package that caches a single call to git rev-parse, enabling O(1) path lookups and seamless operation across main repositories and linked worktrees.
The gastownhall/beads project deeply integrates with Git by utilizing the repository as its underlying storage mechanism. Unlike tools that merely execute Git commands, Beads implements a sophisticated caching layer that parses repository structure once and reuses those results throughout the application lifecycle. This Beads Git integration ensures consistent behavior whether you are working in the main repository or across multiple Git worktrees.
Core Git Integration Architecture
The Cached Git Context Pattern
At the heart of the integration lies a singleton cache implemented in internal/git/gitdir.go. When any Git-related function is first invoked, initGitContext() executes a single subprocess running:
git rev-parse --git-dir --git-common-dir --show-toplevel
This command returns three critical paths: the .git directory, the common directory shared by worktrees, and the repository root. Beads stores these in a gitContext struct protected by sync.Once, ensuring the expensive subprocess spawns exactly once per command execution. Subsequent lookups read from this in-memory cache, providing O(1) access to repository metadata without additional process overhead.
Worktree-Aware Path Resolution
Beads distinguishes between main repositories and linked worktrees by comparing the absolute paths of --git-dir and --git-common-dir. When these differ, the helper function IsWorktree() returns true, signaling that the current working directory resides inside a worktree.
All path-dependent operations—such as GetGitHooksDir() and GetGitRefsDir()—intentionally use the common directory rather than the worktree-specific git-dir. This ensures that hooks, references, and objects remain shared across all worktrees, maintaining consistency when Beads operates on repository-wide data.
Repository Path Management and Normalization
Cross-Platform Path Handling
Before caching paths, Beads normalizes them for cross-platform correctness. The code in internal/git/gitdir.go leverages helper functions (defined in internal/utils/path.go) to convert Windows-style separators, resolve symbolic links, and canonicalize case on macOS and Windows filesystems. This guarantees that string comparisons on paths succeed even when the operating system reports alternate spellings or capitalizations.
Git Hooks and Configuration Paths
The GetGitHooksDir() function respects the core.hooksPath configuration variable, expanding ~ to the user's home directory for cross-platform compatibility. If no custom hooks path is defined, it falls back to <common-dir>/hooks. This ensures that Beads-installed hooks (such as those for Dolt) remain reachable regardless of which worktree is active.
Working with Git Worktrees
Detecting Repository Context from Go
Applications using the Beads library can detect their execution context through the exported API:
package main
import (
"fmt"
"github.com/steveyegge/beads/internal/git"
)
func main() {
// Returns true when the current cwd lives inside a git worktree.
if git.IsWorktree() {
fmt.Println("Running inside a git worktree")
} else {
fmt.Println("Running in the main repository")
}
// Get the absolute path of the repository root (main repo if in a worktree).
root, err := git.GetMainRepoRoot()
if err != nil {
panic(err)
}
fmt.Printf("Main repo root: %s\n", root)
}
The GetMainRepoRoot() function is particularly important for operations requiring repository-wide context, such as exclusive-lock handling or locating the .beads/ metadata directory, because it returns the root of the main repository even when executed from within a nested worktree.
The bd worktree Command Interface
The CLI command bd worktree (implemented in cmd/bd/worktree_cmd.go) provides a thin wrapper around native Git worktree operations:
# List all worktrees that Beads knows about
bd worktree list
# Create a new worktree for a feature branch (Beads will also add it to .gitignore)
bd worktree add feature-login --branch feature/login
# Remove a worktree once the feature is merged
bd worktree remove feature-login
These commands utilize an internal helper called gitCmdInDir() that constructs secure exec.CommandContext instances. Each invocation explicitly sets GIT_TERMINAL_PROMPT=0 and GIT_CONFIG_NOSYSTEM=1 to prevent accidental interactive prompts and system configuration leakage, ensuring deterministic behavior in automated environments.
Accessing Git Hooks Programmatically
When installing or verifying hooks, Beads provides direct access to the resolved hooks directory:
hooksDir, err := git.GetGitHooksDir()
if err != nil {
log.Fatalf("cannot locate hooks: %v", err)
}
fmt.Printf("Hooks live at: %s\n", hooksDir)
This function handles the ~ expansion logic described in lines 28-45 of internal/git/gitdir.go, guaranteeing an absolute path that works correctly on Windows, macOS, and Linux.
Security and Performance Considerations
Concurrency-Safe Caching
The sync.Once primitive guarantees that initGitContext() is safe for concurrent reads within a single command execution. The only mechanism to invalidate this cache is the exported ResetCaches() function, which the test suite uses to ensure clean state between test cases.
Secure Git Command Execution
Beyond caching, Beads emphasizes security when spawning Git processes. The helper gitCmdInDir() constructs commands with environment variables that disable interactive prompts and ignore system-level Git configuration, preventing potential execution of untrusted hooks or credential helpers during automated Beads operations.
Summary
- Single cached call: Beads executes
git rev-parseonce per command to cache repository paths, enabling O(1) lookups viasync.Once. - Worktree native: The integration detects worktrees by comparing
--git-dirand--git-common-dir, ensuring shared resources like hooks remain centralized. - Cross-platform paths: All paths are normalized for Windows, symlinks, and case-insensitive filesystems before caching.
- Secure by default: CLI commands use hardened
exec.CommandContextsettings to prevent prompt blocking and config leakage. - Repository agnostic: Higher-level code remains unaware of worktree specifics, using
GetMainRepoRoot()andIsWorktree()to abstract Git complexity.
Frequently Asked Questions
Does Beads support Git worktrees?
Yes. Beads fully supports Git worktrees by detecting when --git-dir differs from --git-common-dir. All path helpers use the common directory for shared resources while exposing functions like IsWorktree() and GetMainRepoRoot() to let applications adapt their behavior when necessary.
How does Beads locate Git hooks across different worktrees?
Beads resolves the hooks directory through GetGitHooksDir() in internal/git/gitdir.go, which checks core.hooksPath with proper tilde expansion and falls back to <common-dir>/hooks. This ensures hooks are shared across all worktrees rather than duplicated per worktree.
Why is Beads Git integration considered performant?
The integration optimizes performance by caching repository metadata using sync.Once. After the initial call to git rev-parse, all subsequent path lookups read from an in-memory struct, eliminating subprocess overhead and providing O(1) access times for repository metadata.
Is the Git integration safe for concurrent operations?
Yes. The cache initialization is guarded by sync.Once, making it safe for concurrent reads within a single process. The only way to reset the cache is through the exported ResetCaches() function, which is intended for test isolation rather than production use.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →