/{cmd,internal}: fix lint issues
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]) + "..."
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user