feat(hooks): Add git hook infrastructure for Dolt backend
This commit implements the git hook infrastructure for Dolt storage backend as specified in the design document. Changes: - Add `bd hook` command (singular) for git hooks to call directly - Implement per-worktree export state tracking in .beads/export-state/ - Add post-checkout guard to only import if JSONL changed - Add hook chaining configuration (chain_strategy, chain_timeout_ms) - Support hooks in .beads/hooks/ directory with git config core.hooksPath - Implement branch-then-merge import pattern for Dolt storage - Update bd init to install hooks to .beads/hooks/ for Dolt backend - Add --beads flag to `bd hooks install` command The new `bd hook` command supports: - pre-commit: Export database to JSONL, stage changes - post-merge: Import JSONL to database after pull/merge - post-checkout: Import JSONL after branch checkout (with guard) For Dolt backend, uses branch-then-merge pattern: 1. Create jsonl-import branch 2. Import JSONL data to branch 3. Merge branch to main (cell-level conflict resolution) 4. Delete branch on success Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
gastown/crew/dennis
parent
3a7971d2b6
commit
15d74a9a49
@@ -181,6 +181,7 @@ var hooksInstallCmd = &cobra.Command{
|
||||
Long: `Install git hooks for automatic bd sync.
|
||||
|
||||
By default, hooks are installed to .git/hooks/ in the current repository.
|
||||
Use --beads to install to .beads/hooks/ (recommended for Dolt backend).
|
||||
Use --shared to install to a versioned directory (.beads-hooks/) that can be
|
||||
committed to git and shared with team members.
|
||||
|
||||
@@ -197,6 +198,7 @@ Installed hooks:
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
shared, _ := cmd.Flags().GetBool("shared")
|
||||
chain, _ := cmd.Flags().GetBool("chain")
|
||||
beadsHooks, _ := cmd.Flags().GetBool("beads")
|
||||
|
||||
embeddedHooks, err := getEmbeddedHooks()
|
||||
if err != nil {
|
||||
@@ -212,7 +214,7 @@ Installed hooks:
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := installHooks(embeddedHooks, force, shared, chain); err != nil {
|
||||
if err := installHooksWithOptions(embeddedHooks, force, shared, chain, beadsHooks); err != nil {
|
||||
if jsonOutput {
|
||||
output := map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
@@ -227,10 +229,11 @@ Installed hooks:
|
||||
|
||||
if jsonOutput {
|
||||
output := map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "Git hooks installed successfully",
|
||||
"shared": shared,
|
||||
"chained": chain,
|
||||
"success": true,
|
||||
"message": "Git hooks installed successfully",
|
||||
"shared": shared,
|
||||
"chained": chain,
|
||||
"beadsHooks": beadsHooks,
|
||||
}
|
||||
jsonBytes, _ := json.MarshalIndent(output, "", " ")
|
||||
fmt.Println(string(jsonBytes))
|
||||
@@ -241,7 +244,11 @@ Installed hooks:
|
||||
fmt.Println("Mode: chained (existing hooks renamed to .old and will run first)")
|
||||
fmt.Println()
|
||||
}
|
||||
if shared {
|
||||
if beadsHooks {
|
||||
fmt.Println("Hooks installed to: .beads/hooks/")
|
||||
fmt.Println("Git config set: core.hooksPath=.beads/hooks")
|
||||
fmt.Println()
|
||||
} else if shared {
|
||||
fmt.Println("Hooks installed to: .beads-hooks/")
|
||||
fmt.Println("Git config set: core.hooksPath=.beads-hooks")
|
||||
fmt.Println()
|
||||
@@ -319,8 +326,19 @@ var hooksListCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func installHooks(embeddedHooks map[string]string, force bool, shared bool, chain bool) error {
|
||||
return installHooksWithOptions(embeddedHooks, force, shared, chain, false)
|
||||
}
|
||||
|
||||
func installHooksWithOptions(embeddedHooks map[string]string, force bool, shared bool, chain bool, beadsHooks bool) error {
|
||||
var hooksDir string
|
||||
if shared {
|
||||
if beadsHooks {
|
||||
// Use .beads/hooks/ directory (preferred for Dolt backend)
|
||||
beadsDir := beads.FindBeadsDir()
|
||||
if beadsDir == "" {
|
||||
return fmt.Errorf("not in a beads workspace (no .beads directory found)")
|
||||
}
|
||||
hooksDir = filepath.Join(beadsDir, "hooks")
|
||||
} else if shared {
|
||||
// Use versioned directory for shared hooks
|
||||
hooksDir = ".beads-hooks"
|
||||
} else {
|
||||
@@ -373,8 +391,12 @@ func installHooks(embeddedHooks map[string]string, force bool, shared bool, chai
|
||||
}
|
||||
}
|
||||
|
||||
// If shared mode, configure git to use the shared hooks directory
|
||||
if shared {
|
||||
// Configure git to use the hooks directory
|
||||
if beadsHooks {
|
||||
if err := configureBeadsHooksPath(); err != nil {
|
||||
return fmt.Errorf("failed to configure git hooks path: %w", err)
|
||||
}
|
||||
} else if shared {
|
||||
if err := configureSharedHooksPath(); err != nil {
|
||||
return fmt.Errorf("failed to configure git hooks path: %w", err)
|
||||
}
|
||||
@@ -398,6 +420,20 @@ func configureSharedHooksPath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureBeadsHooksPath() error {
|
||||
// Set git config core.hooksPath to .beads/hooks
|
||||
repoRoot := git.GetRepoRoot()
|
||||
if repoRoot == "" {
|
||||
return fmt.Errorf("not in a git repository")
|
||||
}
|
||||
cmd := exec.Command("git", "config", "core.hooksPath", ".beads/hooks")
|
||||
cmd.Dir = repoRoot
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("git config failed: %w (output: %s)", err, string(output))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func uninstallHooks() error {
|
||||
// Get hooks directory from common git dir (hooks are shared across worktrees)
|
||||
hooksDir, err := git.GetGitHooksDir()
|
||||
@@ -1165,6 +1201,7 @@ func init() {
|
||||
hooksInstallCmd.Flags().Bool("force", false, "Overwrite existing hooks without backup")
|
||||
hooksInstallCmd.Flags().Bool("shared", false, "Install hooks to .beads-hooks/ (versioned) instead of .git/hooks/")
|
||||
hooksInstallCmd.Flags().Bool("chain", false, "Chain with existing hooks (run them before bd hooks)")
|
||||
hooksInstallCmd.Flags().Bool("beads", false, "Install hooks to .beads/hooks/ (recommended for Dolt backend)")
|
||||
|
||||
hooksCmd.AddCommand(hooksInstallCmd)
|
||||
hooksCmd.AddCommand(hooksUninstallCmd)
|
||||
|
||||
Reference in New Issue
Block a user