How Incremental Parsing Works in code-review-graph: Workflow and Triggers
code-review-graph uses incremental parsing to update only changed files and their transitive dependents rather than rebuilding the entire knowledge graph from scratch, significantly reducing build times during development.
The tirth8205/code-review-graph repository implements a sophisticated incremental update system in code_review_graph/incremental.py. This module orchestrates change detection, dependency analysis, and selective re-parsing to maintain an accurate SQLite-backed graph store while minimizing redundant work.
The Incremental Parsing Workflow
The incremental parsing pipeline follows a deterministic sequence from change detection to metadata persistence. Each stage is designed to minimize the surface area of code requiring re-analysis.
Step 1: Detecting Repository Changes
The process begins with get_changed_files (line 70 in code_review_graph/incremental.py), which executes git diff --name-status -z <base> to enumerate added, modified, and deleted files since the last known commit. For SVN repositories, it falls back to svn status. This function returns a structured list that drives all downstream decisions.
Step 2: Resolving the Diff Base
When no explicit base commit is provided, resolve_incremental_base (line 44) queries the graph store for the stored git_head_sha metadata. If that commit SHA remains reachable in the repository history, it becomes the diff base; otherwise, the system automatically falls back to a full rebuild to ensure graph integrity.
Step 3: Reconciling Stale Files
Before parsing new changes, _reconcile_stale_files (line 9) purges graph entries for files that have been deleted or newly ignored via .code-review-graphignore. This prevents dangling nodes and ensures the graph accurately reflects the current working tree.
Step 4: Computing Transitive Dependencies
Modified files often impact code that imports or calls them. The find_dependents function (line 85) traverses import and call edges in the graph to discover files that depend on changed symbols. By default, this explores up to _MAX_DEPENDENT_HOPS levels of transitive dependencies, configurable to balance thoroughness versus performance.
Step 5: Parallel Parsing and Storage
The union of changed files, dependent files, and reconciled stale files forms the final parse list. Each file is processed by _parse_single_file (line 27), which invokes CodeParser.parse_bytes to extract nodes and edges using Tree-sitter. Parsing is parallelized via _make_executor using either process or thread pools based on _select_executor_kind heuristics.
Parsed data is written to the SQLite store via store_file_nodes_edges (lines 158–176), replacing outdated entries for those specific files while preserving unchanged portions of the graph.
Step 6: Metadata Update and Language Resolvers
Finally, _store_vcs_metadata updates the stored git_branch, git_head_sha, and CPP_IDENTITY_VERSION. If the C++ identity version has changed, this triggers a full rebuild guard condition. Language-specific resolvers—such as the Python import resolver or Spring DI resolver—execute conditionally only if files of their respective languages appear in the change set (lines 208–236).
What Triggers an Incremental Update?
Several conditions can initiate an incremental build, ranging from automated VCS monitoring to manual API invocation.
VCS Change Detection
The most common trigger occurs when get_changed_files detects non-empty output from git diff or svn status. Any addition, modification, or deletion since the last recorded commit automatically flags the repository for incremental processing.
Explicit API Invocation
Users or CI pipelines trigger updates directly via the Python API:
from pathlib import Path
from code_review_graph.graph import GraphStore
from code_review_graph.incremental import incremental_update
repo = Path("/path/to/repo")
store = GraphStore(Path("/tmp/graph.db"))
# Automatic incremental update based on git changes
result = incremental_update(repo, store)
print(f"Reparsed {result['files_updated']} files")
The base parameter allows targeted comparisons (e.g., base="v1.2.3"), while changed_files accepts a manual list to bypass VCS detection entirely.
Version Mismatches and Stale Entries
Two metadata conditions force rebuilds:
- C++ Identity Version: If the stored
CPP_IDENTITY_VERSIONconstant differs from the current implementation (lines 71–79), the system executes a full rebuild before proceeding. - Unreachable Commits: When
resolve_incremental_basecannot locate the storedgit_head_shain the repository (e.g., after aggressive rebasing), it falls back tofull_buildrather than risk an inconsistent incremental state.
Dependency Propagation
Even when a file itself hasn't changed, updates to its dependencies trigger re-parsing. The find_dependents logic ensures that modifying a shared utility library invalidates and re-parses all consuming modules up to the configurable hop limit.
Implementation Example: Running Full and Incremental Builds
To initialize a new graph store or recover from corruption, use full_build. For daily development workflows, incremental_update provides efficient synchronization:
from code_review_graph.incremental import full_build, incremental_update
# First run or major refactoring: complete rebuild
full_build(repo, store)
# Daily development: incremental sync
result = incremental_update(
repo,
store,
base="HEAD~1", # Optional: specific comparison point
changed_files=None # Optional: override VCS detection
)
The function returns a result dict containing files_updated, allowing callers to log or validate the scope of changes processed.
Summary
- Incremental parsing in
code-review-graphminimizes build times by re-parsing only changed files and their dependents. - The workflow executes in
code_review_graph/incremental.pythrough functions includingget_changed_files,resolve_incremental_base,find_dependents, and_parse_single_file. - Triggers include VCS diffs, manual API calls, unreachable base commits, and
CPP_IDENTITY_VERSIONmismatches. - Dependency propagation uses graph edge traversal to find files impacted by changes to shared dependencies.
- Parallel parsing with automatic executor selection ensures efficient CPU utilization during updates.
Frequently Asked Questions
What happens if the stored base commit no longer exists?
If resolve_incremental_base cannot locate the stored git_head_sha in the repository (for example, after a force push or rebase), the system automatically falls back to full_build. This ensures graph consistency by rebuilding from scratch rather than attempting an incremental update against an unreachable history state.
How does code-review-graph handle file dependencies?
The find_dependents function (line 85) traverses import and call edges in the SQLite graph store to identify files that reference changed symbols. By default, it explores up to _MAX_DEPENDENT_HOPS levels of transitive dependencies, ensuring that modifying a utility file triggers re-parsing of all modules that import it, directly or indirectly.
Can I force a full rebuild instead of an incremental update?
Yes. Call full_build(repo, store) from code_review_graph/incremental.py to completely recreate the knowledge graph. This is recommended for initial indexing, after repository corruption, or when the C++ identity version changes and you want to ensure a clean state before resuming incremental updates.
What languages support incremental resolution?
The core incremental parsing engine is language-agnostic, but post-update resolvers run conditionally based on file extensions detected in the change set. According to the source code, specific implementations include a Python import resolver and a Spring DI resolver, with additional languages supported via the conditional execution blocks near lines 208–236 in incremental.py.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →