Real conflict handling for tsk git-push / git-pull
Push and pull now reconcile through a per-remote shadow at
refs/remotes-tsk/<remote>/* and refuse to silently overwrite a
concurrent edit.
Push:
- Fetch the remote into the shadow first so leases match its current
state.
- For each refs/tsk/* ref, skip when local matches shadow; otherwise
push with `--force-with-lease=<ref>:<shadow-oid>` so a concurrent
push fails the lease and aborts ours.
- After a successful push, refresh the shadow.
Pull:
- Fetch into refs/remotes-tsk/<remote>/* (force, since this is our
private mirror).
- For each fetched ref, look at three OIDs — local, the shadow's
pre-fetch value (the merge base), and the new remote — and decide:
local missing → take remote
local == new remote → no-op
local unchanged from base → take remote
remote unchanged from base → keep local
both moved + mergeable → 3-way merge
both moved + not mergeable → conflict
- Mergeable refs are `log/*` (union sorted by leading timestamp) and
`index` (union preserving local order, append remote-only items).
- Conflicts surface as a clear error listing the divergent refs;
local state is preserved so the user can resolve and retry.
Test covers the happy path (A pushes a new task, B edits a different
task locally, B pulls and gets both sets of changes), and the conflict
path (A and B both edit the same task body, B pulls and gets a
conflict naming the offending ref while keeping their local edit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>