/** * GitFlow Update Policy — Pure decisions for bringing a branch up to date with * its base (feature/release ← develop, hotfix ← main). No I/O, no git. * Shared by the `update` command and `sync --rebase`. */ export type BaseUpdateAction = 'none' | 'ff' | 'merge' | 'rebase'; /** * Pure decision: how to bring the current branch up to date with its base ref. * - nothing to take from the base → 'none' (ahead-only never touches the branch) * - behind with no own commits → 'ff' (cleanest regardless of strategy) * - genuinely diverged → the caller's strategy */ export function decideBaseUpdate(o: { ahead: number; behind: number; strategy: 'merge' | 'rebase'; }): BaseUpdateAction { if (o.behind === 0) return 'none'; if (o.ahead === 0) return 'ff'; return o.strategy; } /** EF Core artifacts in a conflict list (git paths use forward slashes). */ export function isEfCoreConflict(files: string[]): boolean { return files.some((f) => /ModelSnapshot\.cs$/i.test(f) || /(^|\/)Migrations\//i.test(f)); } /** * Human guidance after a rolled-back merge/rebase conflict. The branch is a * no-op at this point (the operation was aborted) — tell the user that, then * how to resolve manually, and point at /efcore rebase-snapshot when the * conflict involves EF Core migration files (the classic snapshot collision). */ export function buildConflictGuidance(o: { operation: 'merge' | 'rebase'; baseRef: string; branch: string; conflictFiles: string[]; }): string { const n = o.conflictFiles.length; let guidance = `${o.operation} of ${o.baseRef} into '${o.branch}' hit conflicts in ${n} file(s) and was aborted — ` + `your branch is untouched. Resolve manually (git ${o.operation === 'merge' ? `merge ${o.baseRef}` : `rebase ${o.baseRef}`}, ` + `fix the conflicts, then conclude), or update the branch in smaller steps.`; if (isEfCoreConflict(o.conflictFiles)) { guidance += ` EF Core migration files conflict — run /efcore rebase-snapshot after updating.`; } return guidance; }