MCPcopy Create free account
hub / github.com/colbymchenry/codegraph / sync

Method sync

src/index.ts:742–966  ·  view source on GitHub ↗

* Sync with current file state (incremental update) * * Uses a mutex to prevent concurrent indexing operations.

(options: IndexOptions = {})

Source from the content-addressed store, hash-verified

740 * Uses a mutex to prevent concurrent indexing operations.
741 */
742 async sync(options: IndexOptions = {}): Promise<SyncResult> {
743 return this.indexMutex.withLock(async () => {
744 try {
745 this.fileLock.acquire();
746 } catch {
747 return { filesChecked: 0, filesAdded: 0, filesModified: 0, filesRemoved: 0, nodesUpdated: 0, durationMs: 0 };
748 }
749 // Defer WAL auto-checkpointing for the whole incremental run, exactly
750 // as indexAll does for the bulk path (#1231): sync's store loop and its
751 // resolution passes churn the same FTS + secondary-index hot pages, and
752 // at the default 1000-page cadence the inline checkpoints re-write them
753 // over and over — on HDD-class storage a 7-file sync took 2 minutes at
754 // 0-2% CPU (#1248). The cost scales with the EXISTING database size,
755 // not the change size, so small syncs on big indexes hurt most. The
756 // valve bounds WAL growth off-thread; runMaintenance at the end does
757 // the final fold-up before the interval is restored in the finally.
758 // Same kill switch as indexAll: CODEGRAPH_NO_WAL_DEFER=1. Idle valve
759 // cost is one timer, so watcher-frequency syncs stay cheap.
760 const deferWal = process.env.CODEGRAPH_NO_WAL_DEFER !== '1' && this.db.getJournalMode() === 'wal';
761 let walValve: WalCheckpointValve | null = null;
762 let priorAutocheckpoint = 1000;
763 if (deferWal) {
764 priorAutocheckpoint = this.db.getWalAutocheckpoint();
765 this.db.setWalAutocheckpoint(0);
766 walValve = new WalCheckpointValve(
767 this.db,
768 resolveWalValveMb(process.env.CODEGRAPH_WAL_VALVE_MB, this.db.getDbFileSizeBytes()),
769 undefined,
770 options.verbose ? (m) => console.log(`[wal-valve] ${m}`) : undefined
771 );
772 walValve.start();
773 }
774 try {
775 // Captured BEFORE the sync runs: the sync's own incremental writes
776 // populate vocab rows for the files it touches, so an end-of-sync
777 // emptiness check would see "non-empty" and skip the backfill forever,
778 // leaving every unchanged file's names unsegmented.
779 const vocabWasEmpty = (() => {
780 try { return this.queries.isNameSegmentVocabEmpty(); } catch { return false; }
781 })();
782
783 const result = await this.orchestrator.sync(options.onProgress, options.paths);
784
785 // Fold the store phase's WAL BEFORE the post-store reads below
786 // (resolution reads on the main thread) — same rationale as
787 // indexAll's fold between store and resolution.
788 if (walValve) await walValve.foldNow();
789
790 // Cross-file finalization (e.g. NestJS RouterModule prefixes). Run on
791 // every sync that touched files so edits to `app.module.ts` propagate
792 // to controllers in unchanged files. The pass is idempotent and cheap
793 // (regex over *.module.ts only).
794 if (result.filesAdded > 0 || result.filesModified > 0) {
795 this.resolver.runPostExtract();
796 } else if (result.filesRemoved > 0) {
797 // A pure-removal sync still resolves refs below — the deletion path
798 // resurrects the removed file's incoming edges as pending refs
799 // (#1240 removal case) and the orphan sweep consumes them. In a

Callers 12

openMethod · 0.95
watchMethod · 0.95
sync.test.tsFile · 0.45
extraction.test.tsFile · 0.45
watcher.test.tsFile · 0.45

Calls 15

resolveWalValveMbFunction · 0.90
getWalAutocheckpointMethod · 0.80
setWalAutocheckpointMethod · 0.80
getDbFileSizeBytesMethod · 0.80
foldNowMethod · 0.80
runPostExtractMethod · 0.80
clearCachesMethod · 0.80
errorMethod · 0.80

Tested by

no test coverage detected