|
| 1 | +# beads-merge |
| 2 | + |
| 3 | +A 3-way merge tool for beads `.jsonl` issue tracker files, designed to work with jj (Jujutsu version control). |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +`beads-merge` intelligently merges beads issue tracker files by: |
| 8 | + |
| 9 | +- Matching issues by their unique key (`.id`, `.created_at`, `.created_by`) |
| 10 | +- Applying smart merge rules for each field |
| 11 | +- Combining dependency arrays and removing duplicates |
| 12 | +- Outputting conflict markers for unresolvable conflicts |
| 13 | + |
| 14 | +## Usage |
| 15 | + |
| 16 | +```bash |
| 17 | +beads-merge <base-file> <left-file> <right-file> |
| 18 | +``` |
| 19 | + |
| 20 | +The tool reads three versions of a `.jsonl` file and outputs the merged result to stdout. |
| 21 | + |
| 22 | +### As a jj Merge Tool |
| 23 | + |
| 24 | +Configure in your jj config (e.g., `~/.jjconfig.toml`): |
| 25 | + |
| 26 | +```toml |
| 27 | +[merge-tools.beads-merge] |
| 28 | +program = "beads-merge" |
| 29 | +merge-args = ["$base", "$left", "$right"] |
| 30 | +``` |
| 31 | + |
| 32 | +Then use it with: |
| 33 | + |
| 34 | +```bash |
| 35 | +jj resolve --tool=beads-merge |
| 36 | +``` |
| 37 | + |
| 38 | +## Merge Algorithm |
| 39 | + |
| 40 | +### Issue Matching |
| 41 | + |
| 42 | +Issues are matched by their composite key: |
| 43 | +- `.id` - Issue identifier |
| 44 | +- `.created_at` - Creation timestamp |
| 45 | +- `.created_by` - Creator identifier |
| 46 | + |
| 47 | +### Field Merging Rules |
| 48 | + |
| 49 | +For matched issues, fields are merged as follows: |
| 50 | + |
| 51 | +**String fields** (title, description, notes, status, issue_type): |
| 52 | +- If base == left and base != right: take right |
| 53 | +- If base == right and base != left: take left |
| 54 | +- Otherwise: take left (including when both changed to same value) |
| 55 | + |
| 56 | +**Priority** (integer): |
| 57 | +- Same logic as string fields |
| 58 | + |
| 59 | +**Timestamps** (updated_at, closed_at): |
| 60 | +- Take the maximum (latest) value |
| 61 | +- If one is null, take the non-null value |
| 62 | + |
| 63 | +**Dependencies** (array): |
| 64 | +- Combine arrays from both sides |
| 65 | +- Remove duplicates based on (issue_id, depends_on_id, type) |
| 66 | + |
| 67 | +### Conflict Handling |
| 68 | + |
| 69 | +Conflicts are output in standard merge conflict format: |
| 70 | + |
| 71 | +``` |
| 72 | +<<<<<<< left |
| 73 | +{"id":"bd-1","title":"Left version",...} |
| 74 | +======= |
| 75 | +{"id":"bd-1","title":"Right version",...} |
| 76 | +>>>>>>> right |
| 77 | +``` |
| 78 | + |
| 79 | +Conflicts occur when: |
| 80 | +- An issue is modified in both branches with incompatible changes to the same field |
| 81 | +- An issue is added in both branches with different content |
| 82 | +- An issue is modified in one branch and deleted in the other |
| 83 | + |
| 84 | +## Building |
| 85 | + |
| 86 | +```bash |
| 87 | +# Run tests |
| 88 | +mise run //beads-merge:test |
| 89 | + |
| 90 | +# Build binary |
| 91 | +mise run //beads-merge:build |
| 92 | + |
| 93 | +# Format code |
| 94 | +mise run //beads-merge:fmt |
| 95 | +``` |
| 96 | + |
| 97 | +## Examples |
| 98 | + |
| 99 | +### Simple merge |
| 100 | + |
| 101 | +Base: |
| 102 | +```jsonl |
| 103 | +{"id":"bd-1","title":"Original","status":"open","created_at":"2025-10-16T20:51:29+02:00","created_by":"user1"} |
| 104 | +``` |
| 105 | + |
| 106 | +Left (updated title): |
| 107 | +```jsonl |
| 108 | +{"id":"bd-1","title":"Updated","status":"open","created_at":"2025-10-16T20:51:29+02:00","created_by":"user1"} |
| 109 | +``` |
| 110 | + |
| 111 | +Right (changed status): |
| 112 | +```jsonl |
| 113 | +{"id":"bd-1","title":"Original","status":"closed","created_at":"2025-10-16T20:51:29+02:00","created_by":"user1"} |
| 114 | +``` |
| 115 | + |
| 116 | +Result (both changes merged): |
| 117 | +```jsonl |
| 118 | +{"id":"bd-1","title":"Updated","status":"closed","created_at":"2025-10-16T20:51:29+02:00","created_by":"user1"} |
| 119 | +``` |
| 120 | + |
| 121 | +### Dependency merge |
| 122 | + |
| 123 | +Left adds dependency on bd-2, right adds dependency on bd-3: |
| 124 | + |
| 125 | +Result combines both dependencies without duplicates. |
| 126 | + |
| 127 | +## Exit Codes |
| 128 | + |
| 129 | +- `0` - Successful merge with no conflicts |
| 130 | +- `1` - Conflicts present (conflict markers in output) or error occurred |
0 commit comments