Fix migrate sync failing in git worktree environments (#970)
Two issues caused `bd migrate sync` to fail when run from a git worktree:
1. Used GetGitDir() instead of GetGitCommonDir() for worktree path
- GetGitDir() returns the worktree-specific path (.bare/worktrees/main)
- GetGitCommonDir() returns the shared git dir (.bare) where new
worktrees can actually be created
2. Used strings.Index instead of LastIndex in GetRepoRoot()
- When user paths contain "worktrees" (e.g., ~/Development/worktrees/),
Index finds the first occurrence and incorrectly strips the path
- LastIndex finds git's internal /worktrees/ directory
Added GetGitCommonDir() to internal/git/gitdir.go for reuse.
Fixes GH#639 (remaining unfixed callsite in migrate_sync.go)
This commit is contained in:
@@ -155,12 +155,12 @@ func runMigrateSync(ctx context.Context, branchName string, dryRun, force bool)
|
|||||||
fmt.Printf("→ Would create new branch '%s'\n", branchName)
|
fmt.Printf("→ Would create new branch '%s'\n", branchName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use worktree-aware git directory detection
|
// Use git-common-dir for worktree path to support bare repos and worktrees (GH#639)
|
||||||
gitDir, err := git.GetGitDir()
|
gitCommonDir, err := git.GetGitCommonDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("not a git repository: %w", err)
|
return fmt.Errorf("not a git repository: %w", err)
|
||||||
}
|
}
|
||||||
worktreePath := filepath.Join(gitDir, "beads-worktrees", branchName)
|
worktreePath := filepath.Join(gitCommonDir, "beads-worktrees", branchName)
|
||||||
fmt.Printf("→ Would create worktree at: %s\n", worktreePath)
|
fmt.Printf("→ Would create worktree at: %s\n", worktreePath)
|
||||||
|
|
||||||
fmt.Println("\n=== END DRY RUN ===")
|
fmt.Println("\n=== END DRY RUN ===")
|
||||||
@@ -195,12 +195,12 @@ func runMigrateSync(ctx context.Context, branchName string, dryRun, force bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Create the worktree
|
// Step 2: Create the worktree
|
||||||
// Use worktree-aware git directory detection
|
// Use git-common-dir for worktree path to support bare repos and worktrees (GH#639)
|
||||||
gitDir, err := git.GetGitDir()
|
gitCommonDir, err := git.GetGitCommonDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("not a git repository: %w", err)
|
return fmt.Errorf("not a git repository: %w", err)
|
||||||
}
|
}
|
||||||
worktreePath := filepath.Join(gitDir, "beads-worktrees", branchName)
|
worktreePath := filepath.Join(gitCommonDir, "beads-worktrees", branchName)
|
||||||
fmt.Printf("→ Creating worktree at %s...\n", worktreePath)
|
fmt.Printf("→ Creating worktree at %s...\n", worktreePath)
|
||||||
|
|
||||||
wtMgr := git.NewWorktreeManager(repoRoot)
|
wtMgr := git.NewWorktreeManager(repoRoot)
|
||||||
|
|||||||
@@ -100,6 +100,23 @@ func GetGitDir() (string, error) {
|
|||||||
return ctx.gitDir, nil
|
return ctx.gitDir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGitCommonDir returns the common git directory shared across all worktrees.
|
||||||
|
// For regular repos, this equals GetGitDir(). For worktrees, this returns
|
||||||
|
// the main repository's .git directory where shared data (like worktree
|
||||||
|
// registrations, hooks, and objects) lives.
|
||||||
|
//
|
||||||
|
// Use this instead of GetGitDir() when you need to create new worktrees or
|
||||||
|
// access shared git data that should not be scoped to a single worktree.
|
||||||
|
// GH#639: This is critical for bare repo setups where GetGitDir() returns
|
||||||
|
// a worktree-specific path that cannot host new worktrees.
|
||||||
|
func GetGitCommonDir() (string, error) {
|
||||||
|
ctx, err := getGitContext()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return ctx.commonDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetGitHooksDir returns the path to the Git hooks directory.
|
// GetGitHooksDir returns the path to the Git hooks directory.
|
||||||
// This function is worktree-aware and handles both regular repos and worktrees.
|
// This function is worktree-aware and handles both regular repos and worktrees.
|
||||||
func GetGitHooksDir() (string, error) {
|
func GetGitHooksDir() (string, error) {
|
||||||
|
|||||||
@@ -987,8 +987,9 @@ func GetRepoRoot(ctx context.Context) (string, error) {
|
|||||||
line := strings.TrimSpace(string(content))
|
line := strings.TrimSpace(string(content))
|
||||||
if strings.HasPrefix(line, "gitdir: ") {
|
if strings.HasPrefix(line, "gitdir: ") {
|
||||||
gitDir := strings.TrimPrefix(line, "gitdir: ")
|
gitDir := strings.TrimPrefix(line, "gitdir: ")
|
||||||
// Remove /worktrees/* part
|
// Remove /worktrees/* part - use LastIndex to handle user paths containing "worktrees"
|
||||||
if idx := strings.Index(gitDir, "/worktrees/"); idx > 0 {
|
// e.g., /Users/foo/worktrees/project/.bare/worktrees/main should strip at .bare/worktrees/
|
||||||
|
if idx := strings.LastIndex(gitDir, "/worktrees/"); idx > 0 {
|
||||||
gitDir = gitDir[:idx]
|
gitDir = gitDir[:idx]
|
||||||
}
|
}
|
||||||
repoRoot = filepath.Dir(gitDir)
|
repoRoot = filepath.Dir(gitDir)
|
||||||
|
|||||||
Reference in New Issue
Block a user