refactor(storage): reuse helpers across the shared-store code (#3352)

One graph-clone helper replaces two copy-then-rename blocks; clean and
status reuse formatSlotSize; leaving a store reuses
removeSharedStorePointer; withStoreLock is imported from its own module
instead of a re-export.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergo Magyar 2026-09-24 10:37:47 +00:00
parent 990ee1b121
commit f9e988ebce
7 changed files with 30 additions and 29 deletions

View file

@ -17,7 +17,7 @@ import {
type RegistryEntry,
} from '../storage/repo-manager.js';
import { requireDeletableStoragePath, StorageDeletionError } from '../storage/storage-resolver.js';
import { formatStaleSlotLine } from './stale-branch-format.js';
import { formatSlotSize, formatStaleSlotLine } from './stale-branch-format.js';
import { listLocalHeads } from '../storage/git.js';
import {
isContainedBranchDir,
@ -157,11 +157,6 @@ const cleanStaleBranchSlots = async (force: boolean): Promise<void> => {
}
};
const formatBytes = (bytes: number): string =>
bytes >= 1024 * 1024
? `${(bytes / (1024 * 1024)).toFixed(1)} MB`
: `${Math.ceil(bytes / 1024)} KB`;
const reportReclaim = (result: ReclaimResult | null): void => {
if (!result) return;
if (result.removed.length > 0) {
@ -222,14 +217,14 @@ export const cleanCommand = async (options?: {
}
if (!options.force) {
console.log(
t('clean.localIndex.preview', { path: legacy.dir, size: formatBytes(legacy.bytes) }),
t('clean.localIndex.preview', { path: legacy.dir, size: formatSlotSize(legacy.bytes) }),
);
console.log(`\n${t('common.runForceConfirm')}`);
return;
}
await removeLegacyLocalIndex(repo.repoPath, repo.storagePath);
console.log(
t('clean.localIndex.deleted', { path: legacy.dir, size: formatBytes(legacy.bytes) }),
t('clean.localIndex.deleted', { path: legacy.dir, size: formatSlotSize(legacy.bytes) }),
);
return;
}

View file

@ -6,6 +6,7 @@
import { resolveGraphPath, storeRootOfCheckoutSlot } from '../storage/shared-store.js';
import { describeSharedGraph, findLegacyLocalIndex } from '../storage/shared-store-lifecycle.js';
import { formatSlotSize } from './stale-branch-format.js';
import path from 'path';
import {
getStoragePaths,
@ -413,7 +414,7 @@ export const statusCommand = async (options: StatusOptions = {}) => {
console.log(
t('status.legacyLocalIndex', {
path: legacyLocalIndex.dir,
size: `${Math.ceil(legacyLocalIndex.bytes / 1024)} KB`,
size: formatSlotSize(legacyLocalIndex.bytes),
}),
);
}

View file

@ -182,8 +182,8 @@ import {
publishSharedGraph,
resolveOptedInStore,
seedSharedSlot,
withStoreLock,
} from './shared-store-analyze.js';
import { withStoreLock } from '../storage/shared-store-lifecycle.js';
import { DEFAULT_PDG_MAX_FUNCTION_LINES } from './ingestion/cfg/collect.js';
import {
DEFAULT_MAX_CFG_EDGES_PER_FUNCTION,

View file

@ -20,7 +20,6 @@ import { constants as fsConstants } from 'fs';
import fs from 'fs/promises';
import path from 'path';
import { acquireIndexLock } from '../storage/index-lock.js';
import { withStoreLock } from '../storage/shared-store-lifecycle.js';
import { commitDistanceToHead, getRemoteUrl, isWorkingTreeDirty } from '../storage/git.js';
import {
readRegistry,
@ -33,13 +32,14 @@ import {
commitGraphDir,
resolveGraphPath,
resolveSharedStore,
SHARED_STORE_POINTER,
sharedStoreLayout,
storeRootOfCheckoutSlot,
type SharedStoreLayout,
} from '../storage/shared-store.js';
import {
reclaimAfterSlotRemoval,
removeSharedStorePointer,
withStoreLock,
writeSharedStorePointer,
} from '../storage/shared-store-lifecycle.js';
import { GITNEXUS_DIR, INDEX_METADATA_FILE, LBUG_DIRECTORY } from '../storage/storage-constants.js';
@ -101,6 +101,22 @@ const exists = (p: string): Promise<boolean> =>
() => false,
);
/**
* Copy a graph file to `dest` via a unique `lbug.new.<id>` temp (swept by the
* slot lock if this process dies mid-copy), cloning copy-on-write where the
* filesystem supports it. On failure the temp is removed and the error thrown.
*/
const cloneGraphFile = async (source: string, dest: string): Promise<void> => {
const tmp = `${dest}.new.${randomUUID()}`;
try {
await fs.copyFile(source, tmp, fsConstants.COPYFILE_FICLONE);
await fs.rename(tmp, dest);
} catch (err) {
await fs.rm(tmp, { force: true }).catch(() => {});
throw err;
}
};
interface CommitGraph {
dir: string;
commit: string;
@ -175,13 +191,9 @@ const seedFromLocalIndex = async (
try {
if (lock.lockFree || (await inspectLbugSidecars(sourceGraph)).kind !== 'clean') return false;
await fs.mkdir(slot, { recursive: true });
const own = path.join(slot, LBUG_DIRECTORY);
const tmp = `${own}.new.${randomUUID()}`;
try {
await fs.copyFile(sourceGraph, tmp, fsConstants.COPYFILE_FICLONE);
await fs.rename(tmp, own);
await cloneGraphFile(sourceGraph, path.join(slot, LBUG_DIRECTORY));
} catch (err) {
await fs.rm(tmp, { force: true }).catch(() => {});
log(`Shared store: could not copy ${sourceGraph} (${(err as Error).message}).`);
return false;
}
@ -245,14 +257,10 @@ export const ensurePrivateSharedGraph = async (slot: string, log: Log): Promise<
const meta = await loadMeta(slot);
if (!meta) return true;
if (!(await exists(own))) {
// `lbug.new.<id>` is swept by the slot lock if this process dies mid-copy.
const tmp = `${own}.new.${randomUUID()}`;
const started = Date.now();
try {
await fs.copyFile(pointed, tmp, fsConstants.COPYFILE_FICLONE);
await fs.rename(tmp, own);
await cloneGraphFile(pointed, own);
} catch (err) {
await fs.rm(tmp, { force: true }).catch(() => {});
const reason = (err as NodeJS.ErrnoException).code ?? (err as Error).message;
log(`Shared store: shared graph unavailable (${reason}); doing a full build.`);
return false;
@ -346,8 +354,6 @@ export const publishSharedGraph = async (
await writeSharedStorePointer(repoPath, layout);
};
export { withStoreLock };
/**
* Store for a checkout that is not a linked worktree: the store named by
* `--share-with`, or the one its registry entry already points into. Clones
@ -414,7 +420,7 @@ export const leaveSharedStore = async (
log: Log,
): Promise<void> => {
await fs.rm(previousSlot, { recursive: true, force: true });
await fs.rm(path.join(repoPath, GITNEXUS_DIR, SHARED_STORE_POINTER), { force: true });
await removeSharedStorePointer(repoPath);
await reclaimAfterSlotRemoval(previousSlot);
log(`Shared store: left ${previousSlot}.`);
};

View file

@ -148,7 +148,7 @@ export const reclaimAfterSlotRemoval = async (
}
};
/** Graph directory a checkout slot reads, for reporting. */
/** Whether a checkout slot reads a shared commit graph or its own private graph. */
export const describeSharedGraph = (
graphPath: string,
storagePath: string,

View file

@ -405,9 +405,8 @@ const inspectCodeIndexDB = async (
// owning one; `resolveGraphPath` only returns a path inside the same store's
// commit graphs, else the slot's own graph.
const graphPath = resolveGraphPath(resolved);
const target = graphPath === lbugPath ? lbugPath : graphPath;
try {
await fsp.access(target);
await fsp.access(graphPath);
return { present: true };
} catch (error) {
const code = (error as NodeJS.ErrnoException)?.code;

View file

@ -3,7 +3,7 @@ import { existsSync } from 'fs';
import fs from 'fs/promises';
import path from 'path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { withStoreLock } from '../../src/core/shared-store-analyze.js';
import { withStoreLock } from '../../src/storage/shared-store-lifecycle.js';
import { loadMeta } from '../../src/storage/repo-manager.js';
import {
resolveSharedStore,