Merging shared boards
pinto stores each board under .pinto/ as plain text so it travels through Git
like the rest of the repository. When two people (or two clones, or two
branches) edit the same board in parallel, Git merges most changes cleanly. The
one place that needs a runbook is new PBIs, because pinto hands out
sequential IDs and two branches that start from the same commit can allocate the
same number.
This chapter explains why those conflicts appear, how to resolve them without
losing history, and how to confirm the merged board is healthy with
pinto doctor.
A ready-to-run board that reproduces the whole scenario lives in
demos/single/merge-conflict;
its README.md walks through the same steps against a disposable clone.
Why parallel clones collide
pinto allocates the next ID by reading .pinto/issued_ids, an append-only list
of every number it has ever issued:
T-1
T-2
T-3
Suppose two branches, alice and bob, both start from a commit whose latest
item is T-1:
alicerunspinto addtwice and allocatesT-2andT-3.bobrunspinto addonce and allocatesT-2.
Both branches independently decided that the next free number was T-2, so
merging them surfaces two kinds of conflict:
- Task-file conflict — both branches created
.pinto/tasks/T-2.mdwith different content, so Git reports anadd/addconflict on that path. issued_idsconflict — because the branches appended a different number of lines, Git reports a content conflict in.pinto/issued_ids.
$ git merge alice
Auto-merging .pinto/issued_ids
CONFLICT (content): Merge conflict in .pinto/issued_ids
Auto-merging .pinto/tasks/T-2.md
CONFLICT (add/add): Merge conflict in .pinto/tasks/T-2.md
Automatic merge failed; fix conflicts and then commit the result.
.pinto/tasks/T-3.md (Alice’s second item) merges cleanly because only one
branch created it.
Resolve issued_ids by taking the union
issued_ids is a history, not a count: a permanently deleted ID must never be
reissued to a different PBI. The correct resolution is therefore always the
union of both sides — keep every number that either branch issued, sorted
and deduplicated. Replace the conflict markers:
T-1
T-2
<<<<<<< HEAD
=======
T-3
>>>>>>> alice
with the union:
T-1
T-2
T-3
Then stage the file:
$ git add .pinto/issued_ids
Resolve the task-file conflict by re-homing one item
Two different items now claim T-2. Pick which one keeps the shared ID, resolve
the file to that item’s content, and stage the rest of the merge:
$ git checkout --theirs .pinto/tasks/T-2.md # keep Alice's item as T-2
$ git add .pinto/tasks/T-2.md .pinto/tasks/T-3.md
$ git commit
Re-home the displaced item under a fresh ID with pinto add. Because you already
unioned issued_ids, pinto add allocates the next free number beyond it
(T-4 here) and appends it to the history:
$ pinto add "Bob X"
$ pinto list
T-1 todo Baseline
T-2 todo Alice A
T-3 todo Alice B
T-4 todo Bob X
Prefer this pinto add re-homing over hand-editing task files: it keeps
issued_ids, the filename, and the frontmatter ID in agreement automatically.
Verify the merged board with pinto doctor
After every merge, run pinto doctor. It scans for the exact damage a bad merge
leaves behind — duplicate IDs, filename/ID mismatches, and issued_ids history
gaps — and prints an explicit repair direction for each finding:
$ pinto doctor
Board is healthy.
If a naive resolution left two files sharing an ID, doctor reports a
duplicate ID finding for each copy:
$ pinto doctor
Found 2 unresolved board issue(s).
[duplicate ID] .pinto/tasks/T-2-alice.md: item ID T-2 is also present at ...
Repair: run pinto doctor --fix to renumber duplicates, or resolve them manually
pinto doctor --fix renumbers the collision deterministically: the first copy
(active tasks before archived items, then by path) keeps the shared ID, and each
later copy is re-homed to a fresh ID above every issued number. The fix rewrites
parent and depends_on references that point at a renumbered copy, appends the
new IDs to issued_ids, and leaves the canonical record untouched:
$ pinto doctor --fix
Found 2 unresolved board issue(s).
Fixed: renumbered T-2 as T-5: .pinto/tasks/T-2-alice.md -> .pinto/tasks/T-5.md
[rank anomaly] .pinto/tasks/T-2.md: rank "j" duplicated in status "todo" parent scope ""
Repair: run pinto rebalance affected workflow scope
[rank anomaly] .pinto/tasks/T-5.md: rank "j" duplicated in status "todo" parent scope ""
Repair: run pinto rebalance affected workflow scope
Independent clones usually allocate the same rank alongside the same ID, so the
two renumbered copies now share a rank in one scope. doctor will not choose
their order for you; run pinto rebalance to spread the collision, then re-run
pinto doctor:
$ pinto rebalance
Rebalanced 2/3 item(s) (max rank length 1 -> 1).
$ pinto doctor
Board is healthy.
Prefer this over hand surgery. If you would rather choose the surviving item
yourself, keep one file and re-home the other with pinto add as above, then
re-run pinto doctor until it prints Board is healthy.
If you accidentally dropped a line from issued_ids while resolving the
conflict, doctor detects the gap and pinto doctor --fix backfills it — it
only records IDs that already belong to existing items and never chooses between
duplicates:
$ pinto doctor
Found 1 unresolved board issue(s).
[issued ID history] .pinto/tasks/T-4.md: item ID T-4 is missing from issued_ids
Repair: append the existing item ID to issued_ids or run pinto doctor --fix
$ pinto doctor --fix
Board is healthy.
Fixed: recorded T-4 in .pinto/issued_ids
Checklist
- Union
.pinto/issued_ids; never drop an issued number. - Preserve both versions of a conflicting task under distinct filenames.
- Run
pinto doctor --fixto renumber duplicates and repair history gaps. - Run
pinto rebalancewhen the renumbered copies collide on rank, then re-runpinto doctoruntil the board is healthy. - Confirm the item list with
pinto listbefore pushing the merge.