(opts: ApplyMutationOptions)
| 65 | * was deleted. |
| 66 | */ |
| 67 | export async function applyMutation(opts: ApplyMutationOptions): Promise<MutationOutcome> { |
| 68 | const { ctx, wantedId, targetStatus, statements, commitMessage } = opts; |
| 69 | const autoCleanup = opts.autoCleanup ?? true; |
| 70 | const branchName = makeWlBranch(ctx.rigHandle, wantedId); |
| 71 | |
| 72 | // ── 1. Idempotency ───────────────────────────────────────────── |
| 73 | // Per the wl-sdk plan: skip the write whenever the branch already |
| 74 | // holds the target status. This deliberately differs from Go's |
| 75 | // `prIdempotent` (mutate.go:116), which only short-circuits when |
| 76 | // `branchStatus == targetStatus && branchStatus != mainStatus` — |
| 77 | // i.e. Go falls through and re-applies the DML when the branch |
| 78 | // matches main, partly to surface a fresh post-mutation result |
| 79 | // and partly to drive auto-cleanup on a stale branch that already |
| 80 | // matches main. |
| 81 | // |
| 82 | // The trade-off: a branch that escaped a previous cleanup attempt |
| 83 | // (network blip on `deleteBranch`, say) and still matches main at |
| 84 | // `targetStatus` will linger here forever — callers must |
| 85 | // `discardBranch` it explicitly. That's acceptable in exchange for |
| 86 | // the simpler "no-op when target reached" guarantee callers expect. |
| 87 | const branchStatus = await readWantedStatusAt({ |
| 88 | auth: ctx.auth, |
| 89 | owner: ctx.fork.forkOwner, |
| 90 | db: ctx.fork.forkDb, |
| 91 | ref: branchName, |
| 92 | wantedId, |
| 93 | fetch: ctx.fetch, |
| 94 | hooks: ctx.hooks, |
| 95 | }); |
| 96 | if (branchStatus === targetStatus) { |
| 97 | return { branchName, alreadyApplied: true, cleanedUp: false }; |
| 98 | } |
| 99 | |
| 100 | // ── 1.5. Stale-fork guard ────────────────────────────────────── |
| 101 | // The DoltHub hosted SQL API doesn't expose any synchronous fork-sync |
| 102 | // primitive (verified by probing the live API: `CALL DOLT_FETCH`, |
| 103 | // `DOLT_PULL`, `DOLT_MERGE('upstream/main', ...)`, and `DOLT_REMOTE` |
| 104 | // all return `Unsupported SQL statement`; the cross-repo PR-from- |
| 105 | // upstream-to-fork mechanic requires write permission on upstream |
| 106 | // which the fork owner doesn't have). So when fork main has fallen |
| 107 | // behind upstream main, we have to bail out and direct the user to |
| 108 | // sync manually — otherwise the WHERE clauses in our DML |
| 109 | // (`WHERE status='open'`, `WHERE claimed_by='<rig>' AND status='claimed'`, |
| 110 | // etc.) will silently match zero rows and the mutation will appear |
| 111 | // to succeed at the API layer while landing nothing. |
| 112 | // |
| 113 | // Skipping the check when the branch already exists at the target |
| 114 | // state is the previous block's job; here we only run for "we're |
| 115 | // about to write something that depends on the branch's base". |
| 116 | await assertForkMainCurrent({ |
| 117 | auth: ctx.auth, |
| 118 | upstream: ctx.upstream, |
| 119 | fork: ctx.fork, |
| 120 | fetch: ctx.fetch, |
| 121 | hooks: ctx.hooks, |
| 122 | }); |
| 123 | |
| 124 | // ── 2. Apply each DML statement ──────────────────────────────── |
no test coverage detected