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>
126 lines
3.4 KiB
Go
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
|
|
}
|
|
}
|