Files
beads/cmd/bd/doctor/fix/repo_fingerprint.go
Steve Yegge 1611f16751 refactor: remove unused bd pin/unpin/hook commands (bd-x0zl)
Analysis found these commands are dead code:
- gt never calls `bd pin` - uses `bd update --status=pinned` instead
- Beads.Pin() wrapper exists but is never called
- bd hook functionality duplicated by gt mol status
- Code comment says "pinned field is cosmetic for bd hook visibility"

Removed:
- cmd/bd/pin.go
- cmd/bd/unpin.go
- cmd/bd/hook.go

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 16:02:15 -08:00

126 lines
3.4 KiB
Go

package fix
import (
"fmt"
"os"
"path/filepath"
"strings"
)
// readLineUnbuffered reads a line from stdin without buffering.
// This ensures subprocess stdin isn't consumed by our buffered reader.
func readLineUnbuffered() (string, error) {
var result []byte
buf := make([]byte, 1)
for {
n, err := os.Stdin.Read(buf)
if err != nil {
return string(result), err
}
if n == 1 {
c := buf[0] // #nosec G602 -- n==1 guarantees buf has 1 byte
if c == '\n' {
return string(result), nil
}
result = append(result, c)
}
}
}
// RepoFingerprint fixes repo fingerprint mismatches by prompting the user
// for which action to take. This is interactive because the consequences
// differ significantly between options:
// 1. Update repo ID (if URL changed or bd upgraded)
// 2. Reinitialize database (if wrong database was copied)
// 3. Skip (do nothing)
func RepoFingerprint(path string) error {
// Validate workspace
if err := validateBeadsWorkspace(path); err != nil {
return err
}
// Get bd binary path
bdBinary, err := getBdBinary()
if err != nil {
return err
}
// Prompt user for action
fmt.Println("\n Repo fingerprint mismatch detected. Choose an action:")
fmt.Println()
fmt.Println(" [1] Update repo ID (if git remote URL changed or bd was upgraded)")
fmt.Println(" [2] Reinitialize database (if wrong .beads was copied here)")
fmt.Println(" [s] Skip (do nothing)")
fmt.Println()
fmt.Print(" Choice [1/2/s]: ")
// Read single character without buffering to avoid consuming input meant for subprocesses
response, err := readLineUnbuffered()
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}
response = strings.TrimSpace(strings.ToLower(response))
switch response {
case "1":
// Run bd migrate --update-repo-id
fmt.Println(" → Running 'bd migrate --update-repo-id'...")
cmd := newBdCmd(bdBinary, "migrate", "--update-repo-id")
cmd.Dir = path
cmd.Stdin = os.Stdin // Allow user to respond to migrate's confirmation prompt
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to update repo ID: %w", err)
}
return nil
case "2":
// Confirm before destructive action
fmt.Print(" ⚠️ This will DELETE .beads/beads.db. Continue? [y/N]: ")
confirm, err := readLineUnbuffered()
if err != nil {
return fmt.Errorf("failed to read confirmation: %w", err)
}
confirm = strings.TrimSpace(strings.ToLower(confirm))
if confirm != "y" && confirm != "yes" {
fmt.Println(" → Skipped (canceled)")
return nil
}
// Remove database and reinitialize
beadsDir := filepath.Join(path, ".beads")
dbPath := filepath.Join(beadsDir, "beads.db")
fmt.Printf(" → Removing %s...\n", dbPath)
if err := os.Remove(dbPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove database: %w", err)
}
// Also remove WAL and SHM files if they exist
_ = os.Remove(dbPath + "-wal")
_ = os.Remove(dbPath + "-shm")
fmt.Println(" → Running 'bd init'...")
cmd := newBdCmd(bdBinary, "init", "--quiet")
cmd.Dir = path
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to initialize database: %w", err)
}
return nil
case "s", "":
fmt.Println(" → Skipped")
return nil
default:
fmt.Printf(" → Unrecognized input '%s', skipping\n", response)
return nil
}
}