fix(worktree): classify orphan unrelated-history merge conflicts (#570) - #578
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes task worktree squash-merge behavior when multiple task branches were created while the base branch had no commits (orphan roots with unrelated history). It improves failure classification so operators get a specific, actionable escalation (unrelated_history) instead of a generic rebase_conflict, and it restores lost conflict file reporting due to a hashtable access bug.
Changes:
- Detect when
Apply-TaskBranchPatchfalls back to the empty-tree base (no merge-base) and classify resulting add/add conflicts asunrelated_history. - Extend merge-failure escalation to include an
unrelated_historypending_question template with dedicated guidance/options. - Fix
Complete-TaskWorktreeconflict file extraction to work with hashtable merge results (restoringconflict_filesreporting for all merge failures).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tests/Test-Components.ps1 | Adds regression coverage for same-file orphan collision; extends kind→template dispatch tests for unrelated_history. |
| src/runtime/Modules/Dotbot.Worktree/Dotbot.Worktree.psm1 | Tracks empty-tree fallback, returns unrelated_history on conflict, and fixes hashtable-safe conflict_files access + messaging. |
| src/runtime/Modules/Dotbot.Task/Dotbot.Task.psm1 | Adds unrelated_history arm to merge-failure pending_question builder. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@carlospedreira Ready for review. |
carlospedreira
left a comment
There was a problem hiding this comment.
Approved. I reviewed the diff and ran layers 1-3 locally; all passed.
Linked issue
Closes #570
Refs #557
Summary of changes
Two task worktrees both cut while the project has no commits are unrelated orphan roots (
--orphanis intentional — the base is the user's repo, so no synthetic bootstrap commit). The first task initializesmain; a second orphan then has no merge-base, soApply-TaskBranchPatchfalls back to the empty-tree base. When both tasks touched the same file, thegit apply --3wayadd/add conflict was returned as the generic, unrecoverablerebase_conflict. (Different-file orphans already merge cleanly — unchanged.)Dotbot.Worktree.psm1— flag the empty-tree fallback and classify the resulting conflict asunrelated_history(vsrebase_conflict), with a distinct completion message.Dotbot.Task.psm1— newunrelated_historypending_questionarm: the task predates the first commit and collides with an earlier task on<files>; operator chooses keep task / keep main / merge by hand (advisory labels).Complete-TaskWorktreereadconflict_filesvia$mergeResult.PSObject.Properties[...], which is always empty for the hashtableApply-TaskBranchPatchreturns — so conflict file lists were silently dropped for every merge failure (incl.rebase_conflict). Now uses hashtable-safe access.Future work (deferred, not in this PR): auto-merge non-overlapping same-file orphan edits by rebasing the orphan branch
--onto <base> --root(by completion timemainholds the earlier task's file → a real 3-way base). Deferred because it's the riskiest merge-path change and its payoff is uncertain for full-rewrite doc collisions (which fall back to this PR's prompt anyway). Revisit only if real runs show frequent auto-resolvable collisions.Testing notes
tests/Test-Components.ps1(Layer 2): new end-to-end same-file orphan-collision test (two orphan worktrees write the same file; first merges → initializesmain; second → assertsfailure_kind = 'unrelated_history'and non-emptyconflict_files), aunrelated_historykind-dispatch case, and an updated source-pin for the refactoredfailure_kindassignment. Regression guard: different-file orphans still merge clean (pre-existing test). Reviewed with/pwsh-review(diff-bug + idioms + history): ship, 0 blockers/majors.PARSE OK.Checklist