Merge bd-xoyh-morsov: GH#517

This commit is contained in:
Steve Yegge
2025-12-16 01:17:18 -08:00
6 changed files with 1004 additions and 20157 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -392,12 +392,20 @@ Use --merge to merge the sync branch back to main branch.`,
if err := ensureStoreActive(); err == nil && store != nil { if err := ensureStoreActive(); err == nil && store != nil {
syncBranchName, _ = syncbranch.Get(ctx, store) syncBranchName, _ = syncbranch.Get(ctx, store)
if syncBranchName != "" && syncbranch.HasGitRemote(ctx) { if syncBranchName != "" && syncbranch.HasGitRemote(ctx) {
repoRoot, err = syncbranch.GetRepoRoot(ctx) // GH#519: Check if sync.branch equals current branch
if err != nil { // If so, we can't use a worktree (git doesn't allow same branch in multiple worktrees)
fmt.Fprintf(os.Stderr, "Warning: sync.branch configured but failed to get repo root: %v\n", err) // Fall back to direct commits on the current branch
fmt.Fprintf(os.Stderr, "Falling back to current branch commits\n") if syncbranch.IsSyncBranchSameAsCurrent(ctx, syncBranchName) {
// sync.branch == current branch - use regular commits, not worktree
useSyncBranch = false
} else { } else {
useSyncBranch = true repoRoot, err = syncbranch.GetRepoRoot(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: sync.branch configured but failed to get repo root: %v\n", err)
fmt.Fprintf(os.Stderr, "Falling back to current branch commits\n")
} else {
useSyncBranch = true
}
} }
} }
} }

View File

@@ -1061,3 +1061,25 @@ func HasGitRemote(ctx context.Context) bool {
} }
return len(strings.TrimSpace(string(output))) > 0 return len(strings.TrimSpace(string(output))) > 0
} }
// GetCurrentBranch returns the name of the current git branch
func GetCurrentBranch(ctx context.Context) (string, error) {
cmd := exec.CommandContext(ctx, "git", "symbolic-ref", "--short", "HEAD")
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get current branch: %w", err)
}
return strings.TrimSpace(string(output)), nil
}
// IsSyncBranchSameAsCurrent returns true if the sync branch is the same as the current branch.
// This is used to detect the case where we can't use a worktree because the branch is already
// checked out. In this case, we should commit directly to the current branch instead.
// See: https://github.com/steveyegge/beads/issues/519
func IsSyncBranchSameAsCurrent(ctx context.Context, syncBranch string) bool {
currentBranch, err := GetCurrentBranch(ctx)
if err != nil {
return false
}
return currentBranch == syncBranch
}

View File

@@ -683,3 +683,70 @@ func TestCountIssuesInContent(t *testing.T) {
}) })
} }
} }
// TestIsSyncBranchSameAsCurrent tests detection of sync.branch == current branch (GH#519)
func TestIsSyncBranchSameAsCurrent(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
ctx := context.Background()
t.Run("returns true when sync branch equals current branch", func(t *testing.T) {
repoDir := setupTestRepo(t)
defer os.RemoveAll(repoDir)
// Create initial commit so we can get current branch
writeFile(t, filepath.Join(repoDir, ".beads", "issues.jsonl"), `{"id":"test-1"}`)
runGit(t, repoDir, "add", ".")
runGit(t, repoDir, "commit", "-m", "initial commit")
// Get current branch name
currentBranch := strings.TrimSpace(getGitOutput(t, repoDir, "symbolic-ref", "--short", "HEAD"))
// Save original dir and change to test repo
origDir, _ := os.Getwd()
os.Chdir(repoDir)
defer os.Chdir(origDir)
// Should return true when sync branch == current branch
if !IsSyncBranchSameAsCurrent(ctx, currentBranch) {
t.Errorf("IsSyncBranchSameAsCurrent(%q) = false, want true", currentBranch)
}
})
t.Run("returns false when sync branch differs from current branch", func(t *testing.T) {
repoDir := setupTestRepo(t)
defer os.RemoveAll(repoDir)
// Create initial commit
writeFile(t, filepath.Join(repoDir, ".beads", "issues.jsonl"), `{"id":"test-1"}`)
runGit(t, repoDir, "add", ".")
runGit(t, repoDir, "commit", "-m", "initial commit")
// Save original dir and change to test repo
origDir, _ := os.Getwd()
os.Chdir(repoDir)
defer os.Chdir(origDir)
// Should return false when sync branch != current branch
if IsSyncBranchSameAsCurrent(ctx, "beads-sync") {
t.Error("IsSyncBranchSameAsCurrent(\"beads-sync\") = true, want false")
}
})
t.Run("returns false on error getting current branch", func(t *testing.T) {
// Test in a non-git directory
tmpDir, _ := os.MkdirTemp("", "non-git-*")
defer os.RemoveAll(tmpDir)
origDir, _ := os.Getwd()
os.Chdir(tmpDir)
defer os.Chdir(origDir)
// Should return false when not in a git repo
if IsSyncBranchSameAsCurrent(ctx, "any-branch") {
t.Error("IsSyncBranchSameAsCurrent in non-git dir = true, want false")
}
})
}

View File

@@ -52,7 +52,7 @@ func ParseIssueType(content string) (types.IssueType, error) {
func ValidatePriority(priorityStr string) (int, error) { func ValidatePriority(priorityStr string) (int, error) {
priority := ParsePriority(priorityStr) priority := ParsePriority(priorityStr)
if priority == -1 { if priority == -1 {
return -1, fmt.Errorf("invalid priority %q (expected 0-4 or P0-P4)", priorityStr) return -1, fmt.Errorf("invalid priority %q (expected 0-4 or P0-P4, not words like high/medium/low)", priorityStr)
} }
return priority, nil return priority, nil
} }