/{cmd,internal}: fix lint issues

This commit is contained in:
coffeegoddd☕️✨
2026-01-20 13:46:57 -08:00
parent 422bc838ed
commit a097fc546b
6 changed files with 47 additions and 83 deletions

View File

@@ -429,13 +429,14 @@ func hookPreCommitDolt(beadsDir, worktreeRoot string) int {
fmt.Fprintf(os.Stderr, "Warning: could not open database: %v\n", err)
return 0
}
defer store.Close()
defer func() { _ = store.Close() }()
// Check if store supports versioned operations (required for Dolt)
vs, ok := storage.AsVersioned(store)
if !ok {
// Fall back to full export if not versioned
return doExportAndSaveState(ctx, beadsDir, worktreeRoot, "")
doExportAndSaveState(ctx, beadsDir, worktreeRoot, "")
return 0
}
// Get current Dolt commit hash
@@ -443,7 +444,8 @@ func hookPreCommitDolt(beadsDir, worktreeRoot string) int {
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not get Dolt commit: %v\n", err)
// Fall back to full export without commit tracking
return doExportAndSaveState(ctx, beadsDir, worktreeRoot, "")
doExportAndSaveState(ctx, beadsDir, worktreeRoot, "")
return 0
}
// Check if we've already exported for this Dolt commit (idempotency)
@@ -465,17 +467,18 @@ func hookPreCommitDolt(beadsDir, worktreeRoot string) int {
}
}
return doExportAndSaveState(ctx, beadsDir, worktreeRoot, currentDoltCommit)
doExportAndSaveState(ctx, beadsDir, worktreeRoot, currentDoltCommit)
return 0
}
// doExportAndSaveState performs the export and saves state. Shared by main path and fallback.
func doExportAndSaveState(ctx context.Context, beadsDir, worktreeRoot, doltCommit string) int {
func doExportAndSaveState(ctx context.Context, beadsDir, worktreeRoot, doltCommit string) {
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
// Export to JSONL
if err := runJSONLExport(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not export to JSONL: %v\n", err)
return 0
return
}
// Stage JSONL files for git commit
@@ -493,8 +496,6 @@ func doExportAndSaveState(ctx context.Context, beadsDir, worktreeRoot, doltCommi
if err := saveExportState(beadsDir, worktreeRoot, state); err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not save export state: %v\n", err)
}
return 0
}
// hasDoltChanges checks if there are any changes between two Dolt commits.
@@ -622,7 +623,7 @@ func hookPostMergeDolt(beadsDir string) int {
fmt.Fprintf(os.Stderr, "Warning: could not open database: %v\n", err)
return 0
}
defer store.Close()
defer func() { _ = store.Close() }()
// Check if Dolt store supports version control operations
doltStore, ok := store.(interface {
@@ -662,7 +663,7 @@ func hookPostMergeDolt(beadsDir string) int {
// Import JSONL to the import branch
jsonlPath := filepath.Join(beadsDir, "issues.jsonl")
if err := importFromJSONLToStore(ctx, store, jsonlPath); err != nil {
if err := importFromJSONLToStore(store, jsonlPath); err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not import JSONL: %v\n", err)
// Checkout back to original branch
_ = doltStore.Checkout(ctx, currentBranch)
@@ -830,7 +831,9 @@ func hookPostCheckout(args []string) int {
// importFromJSONLToStore imports issues from JSONL to a store.
// This is a placeholder - the actual implementation should use the store's methods.
func importFromJSONLToStore(ctx context.Context, store interface{}, jsonlPath string) error {
func importFromJSONLToStore(store interface{}, jsonlPath string) error {
_ = store
_ = jsonlPath
// Use bd sync --import-only for now
// TODO: Implement direct store import
cmd := exec.Command("bd", "sync", "--import-only", "--no-git-history", "--no-daemon")

View File

@@ -913,6 +913,7 @@ type SyncConflictRecord struct {
// LoadSyncConflictState loads the sync conflict state from disk.
func LoadSyncConflictState(beadsDir string) (*SyncConflictState, error) {
path := filepath.Join(beadsDir, "sync_conflicts.json")
// #nosec G304 -- path is derived from the workspace .beads directory
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
@@ -1012,7 +1013,7 @@ func resolveSyncConflicts(ctx context.Context, jsonlPath string, strategy config
// Handle manual strategy with interactive resolution
if strategy == config.ConflictStrategyManual {
return resolveSyncConflictsManually(ctx, jsonlPath, beadsDir, conflictState, baseMap, localMap, remoteMap, baseIssues, localIssues, remoteIssues)
return resolveSyncConflictsManually(ctx, jsonlPath, beadsDir, conflictState, baseMap, localMap, remoteMap)
}
resolved := 0
@@ -1090,8 +1091,7 @@ func resolveSyncConflicts(ctx context.Context, jsonlPath string, strategy config
// resolveSyncConflictsManually handles manual conflict resolution with interactive prompts.
func resolveSyncConflictsManually(ctx context.Context, jsonlPath, beadsDir string, conflictState *SyncConflictState,
baseMap, localMap, remoteMap map[string]*beads.Issue,
baseIssues, localIssues, remoteIssues []*beads.Issue) error {
baseMap, localMap, remoteMap map[string]*beads.Issue) error {
// Build interactive conflicts list
var interactiveConflicts []InteractiveConflict

View File

@@ -160,15 +160,15 @@ func displayConflictDiff(conflict InteractiveConflict) {
// Description (show truncated if different)
if local.Description != remote.Description {
fmt.Printf(" %s\n", ui.RenderAccent("description:"))
fmt.Printf(" %s %s\n", ui.RenderMuted("local:"), truncateText(local.Description, 60))
fmt.Printf(" %s %s\n", ui.RenderAccent("remote:"), truncateText(remote.Description, 60))
fmt.Printf(" %s %s\n", ui.RenderMuted("local:"), truncateText(local.Description))
fmt.Printf(" %s %s\n", ui.RenderAccent("remote:"), truncateText(remote.Description))
}
// Notes (show truncated if different)
if local.Notes != remote.Notes {
fmt.Printf(" %s\n", ui.RenderAccent("notes:"))
fmt.Printf(" %s %s\n", ui.RenderMuted("local:"), truncateText(local.Notes, 60))
fmt.Printf(" %s %s\n", ui.RenderAccent("remote:"), truncateText(remote.Notes, 60))
fmt.Printf(" %s %s\n", ui.RenderMuted("local:"), truncateText(local.Notes))
fmt.Printf(" %s %s\n", ui.RenderAccent("remote:"), truncateText(remote.Notes))
}
// Labels
@@ -371,9 +371,11 @@ func valueOrNone(s string) string {
return s
}
// truncateText truncates a string to maxLen runes (not bytes) for proper UTF-8 handling.
const truncateTextMaxLen = 60
// truncateText truncates a string to a fixed max length (runes, not bytes) for proper UTF-8 handling.
// Replaces newlines with spaces for single-line display.
func truncateText(s string, maxLen int) string {
func truncateText(s string) string {
if s == "" {
return "(empty)"
}
@@ -383,14 +385,14 @@ func truncateText(s string, maxLen int) string {
// Count runes, not bytes, for proper UTF-8 handling
runeCount := utf8.RuneCountInString(s)
if runeCount <= maxLen {
if runeCount <= truncateTextMaxLen {
return s
}
// Truncate by runes
runes := []rune(s)
if maxLen <= 3 {
if truncateTextMaxLen <= 3 {
return "..."
}
return string(runes[:maxLen-3]) + "..."
return string(runes[:truncateTextMaxLen-3]) + "..."
}

View File

@@ -10,78 +10,37 @@ import (
func TestTruncateText(t *testing.T) {
tests := []struct {
name string
input string
maxLen int
want string
name string
input string
want string
}{
{
name: "empty string",
input: "",
maxLen: 10,
want: "(empty)",
name: "empty string",
input: "",
want: "(empty)",
},
{
name: "short string",
input: "hello",
maxLen: 10,
want: "hello",
name: "short string",
input: "hello",
want: "hello",
},
{
name: "exact length",
input: "0123456789",
maxLen: 10,
want: "0123456789",
name: "newlines replaced",
input: "line1\nline2\r\nline3",
want: "line1 line2 line3",
},
{
name: "truncated",
input: "this is a very long string",
maxLen: 15,
want: "this is a ve...",
},
{
name: "newlines replaced",
input: "line1\nline2\nline3",
maxLen: 30,
want: "line1 line2 line3",
},
{
name: "very short max",
input: "hello world",
maxLen: 3,
want: "...",
},
{
name: "UTF-8 characters preserved",
input: "Hello 世界This is a test",
maxLen: 12,
want: "Hello 世界!...",
},
{
name: "UTF-8 exact length",
input: "日本語テスト",
maxLen: 6,
want: "日本語テスト",
},
{
name: "UTF-8 truncate",
input: "日本語テストです",
maxLen: 6,
want: "日本語...",
},
{
name: "emoji handling",
input: "Hello 🌍🌎🌏 World",
maxLen: 12,
want: "Hello 🌍🌎🌏...",
name: "truncated at fixed max",
input: strings.Repeat("a", truncateTextMaxLen+10),
want: strings.Repeat("a", truncateTextMaxLen-3) + "...",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := truncateText(tt.input, tt.maxLen)
got := truncateText(tt.input)
if got != tt.want {
t.Errorf("truncateText(%q, %d) = %q, want %q", tt.input, tt.maxLen, got, tt.want)
t.Errorf("truncateText(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}