88 * Branch model:
99 * - On first run, `legacy-import` is created from the `empty` branch (which
1010 * carries only `.gitsheets/` configs, no records).
11- * - On subsequent runs, the importer opens the gitsheets store, reads the
12- * previous snapshot's record UUIDs at the branch tip
13- * (to keep them stable across re-runs), then opens a single transaction
11+ * - On subsequent runs, the importer reads the previous snapshot's record
12+ * UUIDs through a store pinned at the branch tip (to keep them stable
13+ * across re-runs), then opens a single transaction
1414 * that clears each importer-owned sheet and re-upserts the fresh data.
1515 * - Files are written by gitsheets per each sheet's path template
1616 * (e.g. `people/${{ slug }}.toml`) — the runtime API reads the same paths.
2222 * directly on `transact`).
2323 *
2424 * Side effects:
25- * - Creates the local target branch on first run (from origin/<branch> or
26- * the initial parent)
2725 * - Creates one commit on the local `legacy-import` branch via gitsheets
28- * `transact({ parent, branch })`; the data repo's HEAD is not moved
26+ * `transact({ parent, branch })`. On first run the commit is parented on
27+ * origin/<branch> (or the initial parent) and gitsheets creates the local
28+ * branch. The data repo's HEAD is not moved
2929 * - Does NOT push to origin (operator's call)
3030 *
3131 * Private-store side: out of scope for this importer. The JSON endpoints
3232 * expose only public fields; private data (emails, password hashes,
3333 * newsletter prefs) will be imported separately on a future plan.
3434 */
35- import { execFile } from 'node:child_process' ;
3635import { resolve } from 'node:path' ;
37- import { promisify } from 'node:util' ;
38-
39- const exec = promisify ( execFile ) ;
4036
4137import {
4238 BlogPostSchema ,
@@ -59,9 +55,14 @@ import type {
5955 TagAssignment ,
6056} from '@cfp/shared/schemas' ;
6157
62- import type { Repository } from 'gitsheets' ;
58+ import { openStore , type Repository } from 'gitsheets' ;
6359
64- import { openPublicStore , type PublicStore , type PublicStoreTx } from '../../src/store/public.js' ;
60+ import {
61+ openPublicStore ,
62+ PUBLIC_SHEET_SCHEMAS ,
63+ type PublicStore ,
64+ type PublicStoreTx ,
65+ } from '../../src/store/public.js' ;
6566import { processAvatar } from '../../src/lib/avatar.js' ;
6667import {
6768 fetchAllPages ,
@@ -189,16 +190,16 @@ export async function importLaddrFromJson(opts: ImportOptions): Promise<ImportRe
189190 // against the last even when nothing changed upstream.
190191 // -------------------------------------------------------------------------
191192 let store : PublicStore | null = null ;
192- let branchRef : string | null = null ;
193+ let target : { branchRef : string ; parent : string } | null = null ;
193194 let existingIds : ExistingIds ;
194195
195196 if ( opts . dryRun ) {
196197 existingIds = newExistingIds ( ) ;
197198 } else {
198199 const opened = await openPublicStore ( repo ) ;
199200 store = opened . store ;
200- branchRef = await resolveTargetBranch ( opened . repo , repo , branch , initialParent ) ;
201- existingIds = await collectExistingIdsAt ( store , opened . repo , branchRef , log ) ;
201+ target = await resolveTargetBranch ( opened . repo , repo , branch , initialParent ) ;
202+ existingIds = await collectExistingIdsAt ( opened . repo , target . parent , log ) ;
202203 }
203204
204205 const ctx : TranslateCtx = { idMaps, warnings, now : runAt , existingIds } ;
@@ -481,14 +482,14 @@ export async function importLaddrFromJson(opts: ImportOptions): Promise<ImportRe
481482 // - clear() each importer-owned sheet (deletes capture for free)
482483 // - upsert() every translated record (path-template + schema validation)
483484 // -------------------------------------------------------------------------
484- if ( store === null || branchRef === null ) throw new Error ( '[import-laddr] internal: store not opened' ) ;
485+ if ( store === null || target === null ) throw new Error ( '[import-laddr] internal: store not opened' ) ;
485486
486487 const message = buildCommitMessage ( { runAt, sourceHost : opts . sourceHost , counts } ) ;
487488
488489 const result = await store . transact (
489490 {
490- parent : branchRef ,
491- branch : branchRef ,
491+ parent : target . parent ,
492+ branch : target . branchRef ,
492493 message,
493494 author : { name : AUTHOR_NAME , email : AUTHOR_EMAIL } ,
494495 trailers : {
@@ -626,61 +627,42 @@ function describe(err: unknown): string {
626627// ---------------------------------------------------------------------------
627628
628629/**
629- * Resolve `refs/heads/<branch>` in the data repo, creating it on first run
630- * from `origin/<branch>` if available, falling back to `initialParent`
631- * (typically `origin/empty`). Returns the full ref name; the snapshot
632- * transaction then commits onto it with `transact({ parent, branch })`.
633- *
634- * Creating the ref stays on raw git: gitsheets 2.7's `transact({ branch })`
635- * only advances an existing branch (compare-and-swap against `parent`) and
636- * exposes no public "create branch" call.
630+ * Resolve where the snapshot commits: the local `refs/heads/<branch>` and the
631+ * parent to build on — the branch itself, or on first run `origin/<branch>`
632+ * (falling back to `initialParent`, typically `origin/empty`), resolved to a
633+ * commit. `transact({ parent, branch })` creates the branch when the snapshot
634+ * commits.
637635 */
638636async function resolveTargetBranch (
639637 repo : Repository ,
640638 repoPath : string ,
641639 branch : string ,
642640 initialParent : string ,
643- ) : Promise < string > {
644- const ref = `refs/heads/${ branch } ` ;
645- if ( ( await repo . resolveRef ( ref ) ) !== null ) return ref ;
641+ ) : Promise < { branchRef : string ; parent : string } > {
642+ const branchRef = `refs/heads/${ branch } ` ;
643+ if ( ( await repo . resolveRef ( branchRef ) ) !== null ) return { branchRef , parent : branchRef } ;
646644
647645 const start =
648646 ( await repo . resolveRef ( `refs/remotes/origin/${ branch } ` ) ) ?? ( await repo . resolveRef ( initialParent ) ) ;
649647 if ( start === null ) {
650648 throw new Error (
651- `[import-laddr] cannot create ${ ref } : neither origin/${ branch } nor ${ initialParent } resolves in ${ repoPath } ` ,
649+ `[import-laddr] cannot create ${ branchRef } : neither origin/${ branch } nor ${ initialParent } resolves in ${ repoPath } ` ,
652650 ) ;
653651 }
654- // Create-only (empty old value): fails rather than clobbering a racing writer.
655- await exec ( 'git' , [ 'update-ref' , ref , start , '' ] , { cwd : repoPath } ) ;
656- return ref ;
652+ return { branchRef, parent : start } ;
657653}
658654
659655/**
660- * Read the previous snapshot's ids at the branch tip. gitsheets 2.7 has no
661- * public "sheet at a ref" handle, so this reads inside a transaction parented
662- * on the tip's commit hash and mutates nothing — it finalizes without a
663- * commit and moves no ref. Swap for ref-scoped read-only sheets once
664- * gitsheets ships them.
656+ * Read the previous snapshot's ids at `ref` (the commit the snapshot builds
657+ * on) through a read-only store pinned there.
665658 */
666659async function collectExistingIdsAt (
667- store : PublicStore ,
668660 repo : Repository ,
669661 ref : string ,
670662 log : ( msg : string ) => void ,
671663) : Promise < ExistingIds > {
672- const tip = await repo . resolveRef ( ref ) ;
673- if ( tip === null ) throw new Error ( `[import-laddr] internal: ${ ref } vanished` ) ;
674- const { value, commitHash } = await store . transact (
675- {
676- parent : tip ,
677- message : 'import-laddr: read existing ids (never committed)' ,
678- author : { name : AUTHOR_NAME , email : AUTHOR_EMAIL } ,
679- } ,
680- ( tx ) => collectExistingIds ( tx , log ) ,
681- ) ;
682- if ( commitHash !== null ) throw new Error ( '[import-laddr] internal: read-only transaction committed' ) ;
683- return value ;
664+ const pinned = await openStore ( repo , { validators : PUBLIC_SHEET_SCHEMAS , ref } ) ;
665+ return collectExistingIds ( pinned , log ) ;
684666}
685667
686668// ---------------------------------------------------------------------------
0 commit comments