mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-27 21:55:14 +08:00
fix(init): refuse a codegraph.db that is not SQLite instead of promising a rebuild
Review found that a `.codegraph/codegraph.db` holding bytes that are not SQLite at all counted as "schema-less", so `init` printed "rebuilding it" and then failed with SQLite's "file is not a database". Only an empty file or a SQLite database without the codegraph tables can be rebuilt in place; `hasSchemalessDb` now means exactly that, and the new `hasForeignDbFile` names the other case. `init` stops on it with exit 1, says which file it is and to move or delete it, and never touches it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Forge
co-authored by
Claude Opus 5.5
parent
24041f38b9
commit
1547f9332a
@@ -178,4 +178,33 @@ describe('a schema-less codegraph.db is not an initialized project (#1895)', ()
|
||||
db.close();
|
||||
expect(row.c).toBe(1);
|
||||
});
|
||||
|
||||
it('(d) an empty file is repaired too; a file that is not SQLite is refused with guidance and left alone', () => {
|
||||
plantBrokenDb(parent, 'empty');
|
||||
fs.writeFileSync(path.join(parent, 'a.py'), 'def alpha():\n return 1\n');
|
||||
const empty = run(parent, ['init', '--yes']);
|
||||
expect(empty.status).toBe(0);
|
||||
expect(empty.out).toContain('without the codegraph schema');
|
||||
expect(isInitialized(parent)).toBe(true);
|
||||
|
||||
for (const bytes of ['x'.repeat(4096), 'hello']) {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-1895-foreign-'));
|
||||
try {
|
||||
const cgDir = path.join(dir, '.codegraph');
|
||||
fs.mkdirSync(cgDir);
|
||||
const dbPath = path.join(cgDir, 'codegraph.db');
|
||||
fs.writeFileSync(dbPath, bytes);
|
||||
fs.writeFileSync(path.join(dir, 'a.py'), 'def alpha():\n return 1\n');
|
||||
const r = run(dir, ['init', '--yes']);
|
||||
expect(r.status).toBe(1);
|
||||
expect(r.out).toContain('is not a SQLite database');
|
||||
expect(r.out).not.toContain('rebuilding');
|
||||
expect(r.out).not.toContain('file is not a database');
|
||||
// Nothing is deleted or rewritten behind the user's back.
|
||||
expect(fs.readFileSync(dbPath, 'utf-8')).toBe(bytes);
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ try {
|
||||
import { Command } from 'commander';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { getCodeGraphDir, isInitialized, hasSchemalessDb, unsafeIndexRootReason, findNearestCodeGraphRoot, planFrontload, hasStructuralKeyword, extractCodeTokens, capPromptHookInjection } from '../directory';
|
||||
import { getCodeGraphDir, isInitialized, hasSchemalessDb, hasForeignDbFile, unsafeIndexRootReason, findNearestCodeGraphRoot, planFrontload, hasStructuralKeyword, extractCodeTokens, capPromptHookInjection } from '../directory';
|
||||
import { extractProseCandidates } from '../search/identifier-segments';
|
||||
import { detectWorktreeIndexMismatch, worktreeMismatchWarning } from '../sync/worktree';
|
||||
import { createShimmerProgress } from '../ui/shimmer-progress';
|
||||
@@ -697,6 +697,14 @@ async function runInit(
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasForeignDbFile(projectPath)) {
|
||||
const dbFile = path.join(getCodeGraphDir(projectPath), 'codegraph.db');
|
||||
clack.log.error(`${dbFile} is not a SQLite database, so it cannot be rebuilt in place.`);
|
||||
clack.log.info('Move or delete that file, then run "codegraph init" again.');
|
||||
clack.outro('');
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (hasSchemalessDb(projectPath)) {
|
||||
clack.log.warn(`Found a codegraph.db without the codegraph schema in ${getCodeGraphDir(projectPath)} (left by an interrupted init?) — rebuilding it.`);
|
||||
}
|
||||
|
||||
+23
-2
@@ -119,10 +119,31 @@ export function isInitialized(projectRoot: string): boolean {
|
||||
return hasCodeGraphSchema(dbPath, st);
|
||||
}
|
||||
|
||||
/** `codegraph.db` exists at `projectRoot` but does not carry the schema (#1895). */
|
||||
/**
|
||||
* `codegraph.db` exists at `projectRoot` but does not carry the schema, and
|
||||
* `init` can add it in place: an empty file, or a SQLite database without the
|
||||
* codegraph tables (#1895). A file that is not SQLite at all is NOT this case —
|
||||
* see {@link hasForeignDbFile}.
|
||||
*/
|
||||
export function hasSchemalessDb(projectRoot: string): boolean {
|
||||
const dbPath = path.join(getCodeGraphDir(projectRoot), 'codegraph.db');
|
||||
return fs.existsSync(dbPath) && !isInitialized(projectRoot);
|
||||
let st: fs.Stats;
|
||||
try { st = fs.statSync(dbPath); } catch { return false; }
|
||||
if (!st.isFile() || isInitialized(projectRoot)) return false;
|
||||
return st.size === 0 || readsAsSqlite(dbPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* `codegraph.db` exists at `projectRoot` and is not a SQLite database (no
|
||||
* header magic): SQLite refuses to open it, so `init` cannot rebuild it in
|
||||
* place. The caller must say so rather than promise a repair; nothing here
|
||||
* deletes the file.
|
||||
*/
|
||||
export function hasForeignDbFile(projectRoot: string): boolean {
|
||||
const dbPath = path.join(getCodeGraphDir(projectRoot), 'codegraph.db');
|
||||
let st: fs.Stats;
|
||||
try { st = fs.statSync(dbPath); } catch { return false; }
|
||||
return st.isFile() && st.size > 0 && !readsAsSqlite(dbPath);
|
||||
}
|
||||
|
||||
const SQLITE_MAGIC = Buffer.from('SQLite format 3\0', 'latin1');
|
||||
|
||||
Reference in New Issue
Block a user