diff --git a/AGENTS.md b/AGENTS.md index 4299c105..9833e83e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -602,17 +602,27 @@ The daemon maintains its own view of the current working directory and git state **With hash-based IDs (v0.20.1+), ID collisions are eliminated!** Different issues get different hash IDs, so most git merges succeed cleanly. **When git merge conflicts occur:** -Git conflicts in `.beads/beads.jsonl` happen when the same issue is modified on both branches (different timestamps/fields). This is a **same-issue update conflict**, not an ID collision. +Git conflicts in `.beads/beads.jsonl` happen when the same issue is modified on both branches (different timestamps/fields). This is a **same-issue update conflict**, not an ID collision. Conflicts are rare in practice since hash IDs prevent structural collisions. -**Resolution:** +**Automatic detection:** +bd automatically detects conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) and shows clear resolution steps: +- `bd import` rejects files with conflict markers and shows resolution commands +- `bd validate --checks=conflicts` scans for conflicts in JSONL + +**Resolution workflow:** ```bash -# After git merge creates conflict -git checkout --theirs .beads/beads.jsonl # Accept remote version -# OR -git checkout --ours .beads/beads.jsonl # Keep local version -# OR manually resolve in editor +# After git merge creates conflict in .beads/beads.jsonl -# Import the resolved JSONL +# Option 1: Accept their version (remote) +git checkout --theirs .beads/beads.jsonl +bd import -i .beads/beads.jsonl + +# Option 2: Keep our version (local) +git checkout --ours .beads/beads.jsonl +bd import -i .beads/beads.jsonl + +# Option 3: Manual resolution in editor +# Edit .beads/beads.jsonl to remove conflict markers bd import -i .beads/beads.jsonl # Commit the merge @@ -620,7 +630,7 @@ git add .beads/beads.jsonl git commit ``` -**bd automatically handles updates** - same ID with different content is a normal update operation. No special flags needed. +**Note:** `bd import` automatically handles updates - same ID with different content is a normal update operation. No special flags needed. If you accidentally modified the same issue in both branches, just pick whichever version is more complete. ### Advanced: Intelligent Merge Tools diff --git a/cmd/bd/import.go b/cmd/bd/import.go index c67478ec..99ead5d7 100644 --- a/cmd/bd/import.go +++ b/cmd/bd/import.go @@ -59,24 +59,34 @@ Behavior: lineNum := 0 for scanner.Scan() { - lineNum++ - line := scanner.Text() + lineNum++ + line := scanner.Text() - // Skip empty lines - if line == "" { - continue - } - - // Parse JSON - var issue types.Issue - if err := json.Unmarshal([]byte(line), &issue); err != nil { - fmt.Fprintf(os.Stderr, "Error parsing line %d: %v\n", lineNum, err) - os.Exit(1) - } - - allIssues = append(allIssues, &issue) + // Skip empty lines + if line == "" { + continue } + // Detect git conflict markers + if strings.Contains(line, "<<<<<<<") || strings.Contains(line, "=======") || strings.Contains(line, ">>>>>>>") { + fmt.Fprintf(os.Stderr, "Error: Git conflict markers detected in JSONL file (line %d)\n\n", lineNum) + fmt.Fprintf(os.Stderr, "To resolve:\n") + fmt.Fprintf(os.Stderr, " git checkout --ours .beads/issues.jsonl && bd import -i .beads/issues.jsonl\n") + fmt.Fprintf(os.Stderr, " git checkout --theirs .beads/issues.jsonl && bd import -i .beads/issues.jsonl\n\n") + fmt.Fprintf(os.Stderr, "For advanced field-level merging, see: https://github.com/neongreen/mono/tree/main/beads-merge\n") + os.Exit(1) + } + + // Parse JSON + var issue types.Issue + if err := json.Unmarshal([]byte(line), &issue); err != nil { + fmt.Fprintf(os.Stderr, "Error parsing line %d: %v\n", lineNum, err) + os.Exit(1) + } + + allIssues = append(allIssues, &issue) + } + if err := scanner.Err(); err != nil { fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err) os.Exit(1) diff --git a/cmd/bd/validate.go b/cmd/bd/validate.go index f73bae0e..cf6e57ef 100644 --- a/cmd/bd/validate.go +++ b/cmd/bd/validate.go @@ -400,17 +400,21 @@ func validateGitConflicts(_ context.Context, fix bool) checkResult { if len(conflictLines) > 0 { result.issueCount = 1 // One conflict situation result.suggestions = append(result.suggestions, - fmt.Sprintf("Resolve git conflict in %s (markers at lines: %v)", jsonlPath, conflictLines)) - if !fix { - result.suggestions = append(result.suggestions, - fmt.Sprintf("Then run 'bd import -i %s' to reload issues", jsonlPath)) - } + fmt.Sprintf("Git conflict markers found in %s at lines: %v", jsonlPath, conflictLines)) + result.suggestions = append(result.suggestions, + "To resolve, choose one version:") + result.suggestions = append(result.suggestions, + " git checkout --ours .beads/issues.jsonl && bd import -i .beads/issues.jsonl") + result.suggestions = append(result.suggestions, + " git checkout --theirs .beads/issues.jsonl && bd import -i .beads/issues.jsonl") + result.suggestions = append(result.suggestions, + "For advanced field-level merging: https://github.com/neongreen/mono/tree/main/beads-merge") } // Can't auto-fix git conflicts if fix && result.issueCount > 0 { result.suggestions = append(result.suggestions, - "Git conflicts cannot be auto-fixed. Resolve manually in your editor or run 'bd export' to regenerate JSONL") + "Note: Git conflicts cannot be auto-fixed with --fix-all") } return result