diff --git a/README.md b/README.md index d73c90c..e24afe6 100644 --- a/README.md +++ b/README.md @@ -79,16 +79,18 @@ pnpm cella sync --track branch # follow the tip once, without editing config main ──▶ cella/sync/ ──(3-way merge)──▶ PR ──(squash)──▶ main ``` -It runs a real git 3-way merge and leaves the result **staged** — it never auto-commits on the -first pass. `sync` is **idempotent and two-phase**: - -1. **First run** cuts the branch and stages the merge, then stops so you can review (and resolve - any conflicts in your IDE — `git add` the resolved files). -2. **Re-run `pnpm cella sync`** on the same branch to finish: it reconciles dependencies - (`pnpm install` + `pnpm check`), stages everything, commits the delta, pushes to `origin`, +It runs a real git 3-way merge. `sync` is **idempotent and staged**: each run advances the sync +one stage, and the run that commits never ships — the pause on the committed branch is where +drift triage (`pnpm cella analyze` diffs committed HEAD) and follow-up commits happen. + +1. **First run** cuts the branch and merges. A clean merge is committed right away: dependencies + are reconciled (`pnpm install` + `pnpm check`), everything is staged, and the delta is + committed — then the run stops on the branch. A conflicted merge stops earlier so you can + resolve in your IDE (`git add` the resolved files) and re-run to commit. +2. **Final re-run `pnpm cella sync`** on the committed branch ships it: pushes to `origin`, opens a PR into `main` (via `gh`), and switches you back to `main`. -When you commit, the in-progress merge state (`MERGE_HEAD`) is discarded, so the staged delta +When the commit stage runs, the in-progress merge state (`MERGE_HEAD`) is discarded, so the staged delta collapses into a **single-parent commit** (`chore: sync upstream cella `). This keeps the PR to one clean commit with the incremental diff — a two-parent merge commit would instead list the upstream branch's entire history, because the fork doesn't share pushed ancestry with upstream diff --git a/src/services/sync.ts b/src/services/sync.ts index 87a4a8e..104483a 100644 --- a/src/services/sync.ts +++ b/src/services/sync.ts @@ -300,7 +300,7 @@ function printShipSteps(temporaryBranch: string, base: string, title?: string): printPrCreateStep(temporaryBranch, base, title); } -/** Guidance shown after a fresh cycle stages a merge: re-run to finish and ship it. */ +/** Guidance shown after a fresh cycle stops at conflicts: re-run to commit once resolved. */ function printFinishSteps(): void { console.info(pc.dim(' pnpm cella sync')); } @@ -370,7 +370,8 @@ function enableAutoMerge(forkPath: string, branch: string): boolean { /** * Push the finished sync branch to `origin`, open a PR into the trunk, and switch back to the - * trunk. Runs automatically once a rerun completes the merge cleanly. + * trunk. Runs when `cella sync` is invoked on a sync branch whose merge is already committed — + * shipping is always its own run, after the commit stage stopped for drift triage. * * Before pushing, any merge commits on the branch are flattened away (see `flattenSyncBranch`) * so the PR never lists the upstream branch's entire history. @@ -503,15 +504,16 @@ async function runSyncCycle(config: RuntimeConfig): Promise { } /** - * Finish an in-progress merge left by an earlier `cella sync` run on the same temporary branch. + * Commit an in-progress merge on the temporary sync branch — never shipping in the same run. * - * This is what makes the command idempotent: after a run stops at conflicts, resolve and stage - * them, then run `cella sync` again. If conflicts remain we point them out and stop; once none - * remain we reconcile dependencies (`pnpm install` + `pnpm check`), stage everything, commit the - * staged delta as a single squashed commit (see `commitSquash`), then push the branch and open - * the PR (see `shipSyncBranch`). + * Runs directly after a clean merge, or on a rerun once a conflicted merge is resolved and + * staged. If conflicts remain we point them out and stop; once none remain we reconcile + * dependencies (`pnpm install` + `pnpm check`), stage everything, and commit the staged delta + * as a single squashed commit (see `commitSquash`). It then stops on the committed branch: + * that is the window for drift triage (`cella analyze` diffs committed HEAD) and follow-up + * commits. Shipping (push + PR) is always its own rerun (see `runSyncCommand`). */ -async function resumeSyncMerge(config: RuntimeConfig, branch: string): Promise { +async function commitSyncMerge(config: RuntimeConfig, branch: string): Promise { const { forkPath } = config; const conflicts = await getConflictedFiles(forkPath); @@ -547,7 +549,14 @@ async function resumeSyncMerge(config: RuntimeConfig, branch: string): Promise { const { forkPath } = config; @@ -658,9 +669,9 @@ export async function runSyncCommand(config: RuntimeConfig): Promise { const onSyncBranch = isTemporarySyncBranch(currentBranch); // Resume path: an earlier run left a merge staged on this temporary branch (e.g. after - // conflicts). Re-running finishes it instead of starting over. + // conflicts). Re-running commits it instead of starting over. if (onSyncBranch && mergeInProgress(forkPath)) { - await resumeSyncMerge(config, currentBranch); + await commitSyncMerge(config, currentBranch); return; } @@ -701,7 +712,11 @@ export async function runSyncCommand(config: RuntimeConfig): Promise { if (outcome.status === 'conflicts') { console.info(`${warningMark} ${pc.yellow(`conflicts on '${temporaryBranch}'. Resolve and stage them, then:`)}`); printFinishSteps(); - console.info(pc.dim(' rerun commits the sync, pushes the branch, and opens a PR.')); + console.info(pc.dim(' rerun commits the sync and stops for drift triage; a further rerun ships (push + PR).')); console.info(pc.dim(' let the rerun commit — a manual `git commit` records a merge commit that bloats the PR.')); + return; } + + // Clean merge: commit it in the same run (never shipping — that stays a separate rerun). + await commitSyncMerge(config, temporaryBranch); } diff --git a/src/utils/display.ts b/src/utils/display.ts index 6e9c69b..a806531 100644 --- a/src/utils/display.ts +++ b/src/utils/display.ts @@ -552,9 +552,7 @@ export function printSyncComplete(result: MergeResult, options: { stagedBranch?: if (options.stagedBranch) { console.info(`${pc.green('✓')} Sync merge staged on '${options.stagedBranch}'`); console.info( - pc.dim( - ` ${updated} files updated, ${merged} auto-merged, ${conflicts} conflicts. Review, then rerun \`pnpm cella sync\` to finish.`, - ), + pc.dim(` ${updated} files updated, ${merged} auto-merged, ${conflicts} conflicts. Committing next...`), ); } else { console.info(`${pc.green('✓')} sync complete`);