feat(hooks): add jujutsu (jj) version control support

Add detection and hook support for jujutsu repositories:

- IsJujutsuRepo(): detects .jj directory
- IsColocatedJJGit(): detects colocated jj+git repos
- GetJujutsuRoot(): finds jj repo root

For colocated repos (jj git init --colocate):
- Install simplified hooks without staging (jj auto-commits working copy)
- Worktree handling preserved for git worktrees in colocated repos

For pure jj repos (no git):
- Print alias instructions since jj doesn't have native hooks yet

Closes: hq-ew1mbr.12

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beads/crew/lizzy
2026-01-20 19:12:51 -08:00
committed by Steve Yegge
parent e00f013bda
commit 2fe15e2328
4 changed files with 462 additions and 17 deletions

View File

@@ -2,6 +2,7 @@ package git
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
@@ -271,3 +272,45 @@ func ResetCaches() {
gitCtxOnce = sync.Once{}
gitCtx = gitContext{}
}
// IsJujutsuRepo returns true if the current directory is in a jujutsu (jj) repository.
// Jujutsu stores its data in a .jj directory at the repository root.
func IsJujutsuRepo() bool {
_, err := GetJujutsuRoot()
return err == nil
}
// IsColocatedJJGit returns true if this is a colocated jujutsu+git repository.
// Colocated repos have both .jj and .git directories, created via `jj git init --colocate`.
// In colocated repos, git hooks work normally since jj manages the git repo.
func IsColocatedJJGit() bool {
if !IsJujutsuRepo() {
return false
}
// If we're also in a git repo, it's colocated
_, err := getGitContext()
return err == nil
}
// GetJujutsuRoot returns the root directory of the jujutsu repository.
// Returns empty string and error if not in a jujutsu repository.
func GetJujutsuRoot() (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("failed to get current directory: %w", err)
}
dir := cwd
for {
jjPath := filepath.Join(dir, ".jj")
if info, err := os.Stat(jjPath); err == nil && info.IsDir() {
return dir, nil
}
parent := filepath.Dir(dir)
if parent == dir {
return "", fmt.Errorf("not a jujutsu repository (no .jj directory found)")
}
dir = parent
}
}