( gitRoot: string, bundlePath: string, maxBytes: number, hasStash: boolean, signal: AbortSignal | undefined, )
| 48 | // commit of HEAD's tree (or the stash tree if WIP exists) — no history, |
| 49 | // just the snapshot. Receiver needs refs/seed/root handling for that tier. |
| 50 | async function _bundleWithFallback( |
| 51 | gitRoot: string, |
| 52 | bundlePath: string, |
| 53 | maxBytes: number, |
| 54 | hasStash: boolean, |
| 55 | signal: AbortSignal | undefined, |
| 56 | ): Promise<BundleCreateResult> { |
| 57 | // --all picks up refs/seed/stash; HEAD needs it explicit. |
| 58 | const extra = hasStash ? ['refs/seed/stash'] : [] |
| 59 | const mkBundle = (base: string) => |
| 60 | execFileNoThrowWithCwd( |
| 61 | gitExe(), |
| 62 | ['bundle', 'create', bundlePath, base, ...extra], |
| 63 | { cwd: gitRoot, abortSignal: signal }, |
| 64 | ) |
| 65 | |
| 66 | const allResult = await mkBundle('--all') |
| 67 | if (allResult.code !== 0) { |
| 68 | return { |
| 69 | ok: false, |
| 70 | error: `git bundle create --all failed (${allResult.code}): ${allResult.stderr.slice(0, 200)}`, |
| 71 | failReason: 'git_error', |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | const { size: allSize } = await stat(bundlePath) |
| 76 | if (allSize <= maxBytes) { |
| 77 | return { ok: true, size: allSize, scope: 'all' } |
| 78 | } |
| 79 | |
| 80 | // bundle create overwrites in place. |
| 81 | logForDebugging( |
| 82 | `[gitBundle] --all bundle is ${(allSize / 1024 / 1024).toFixed(1)}MB (> ${(maxBytes / 1024 / 1024).toFixed(0)}MB), retrying HEAD-only`, |
| 83 | ) |
| 84 | const headResult = await mkBundle('HEAD') |
| 85 | if (headResult.code !== 0) { |
| 86 | return { |
| 87 | ok: false, |
| 88 | error: `git bundle create HEAD failed (${headResult.code}): ${headResult.stderr.slice(0, 200)}`, |
| 89 | failReason: 'git_error', |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | const { size: headSize } = await stat(bundlePath) |
| 94 | if (headSize <= maxBytes) { |
| 95 | return { ok: true, size: headSize, scope: 'head' } |
| 96 | } |
| 97 | |
| 98 | // Last resort: squash to a single parentless commit. Uses the stash tree |
| 99 | // when WIP exists (bakes uncommitted changes in — can't bundle the stash |
| 100 | // ref separately since its parents would drag history back). |
| 101 | logForDebugging( |
| 102 | `[gitBundle] HEAD bundle is ${(headSize / 1024 / 1024).toFixed(1)}MB, retrying squashed-root`, |
| 103 | ) |
| 104 | const treeRef = hasStash ? 'refs/seed/stash^{tree}' : 'HEAD^{tree}' |
| 105 | const commitTree = await execFileNoThrowWithCwd( |
| 106 | gitExe(), |
| 107 | ['commit-tree', treeRef, '-m', 'seed'], |
no test coverage detected