Skip to content

feat(status): answer pending changes from git's candidates when git sees every change (#1878) - #289

Merged
sunerpy merged 10 commits into
mainfrom
sync/v1.6.1-pending-fast-path
Oct 1, 2026
Merged

sunerpy merged 10 commits into
mainfrom
sync/v1.6.1-pending-fast-path

Conversation

@sunerpy

@sunerpy sunerpy commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

This ports upstream #1878's git-stamped fast path for pending status (#1829), as the owner decided on 2026-10-01. The design passed the kirocodex plan-convergence review in round 4 (blocking items 6 → 1 → 2 → 0).

The full inventory stays the reference answer and the fallback. The fast path only ever returns what the inventory would.

Record (crates/codegraph-watch/src/git_pending.rs)

  • A full build (CLI index, migration) or full sync captures git before it scans: HEAD, the status set, and whether there are submodules. Before finishing, it writes one project_metadata value, git_pending_state: {v, commit, dirty, scope, links, submodules}. No DDL.
  • dirty is the status set before and after the build, every row whose stat moved while the build ran, and every path where a fresh scan and the database disagree. The last two cover a file read in a transient state and restored, and a file the scan missed while it was briefly gone or excluded.
  • scope fingerprints everything that decides scan membership: the config scan fields, extension overrides, root .gitignore, reserved index roots, extraction version, and binary version. A scope that changes during the build writes no record.
  • A full sync forgets the record before any row moves. An incremental sync adds its requested paths to dirty before it writes, or forgets the record if the scope moved.

Reader

  • Candidates: git diff <commit> HEAD, plus git status -uall, plus dirty. They are classified by the scan's own membership (scan_membership) and the inventory's stat-then-hash rules. Git supplies candidates, never the verdict.
  • Declines to the full inventory when:
    • there is no record, a different scope, links, or submodules;
    • there is no HEAD, or the recorded commit is unknown;
    • git may call changed bytes or names clean: core.autocrlf, any gitattributes source (resolved through git var GIT_ATTR_SYSTEM / GIT_ATTR_GLOBAL, plus info/attributes and .gitattributes), core.ignorecase, core.precomposeunicode. Windows CI caught the first: a revert rewrote LF as CRLF while git status stayed clean. So on macOS and Windows defaults the full inventory answers;
    • an untracked nested repository, a directory or symlink candidate, or an assume-unchanged/skip-worktree entry is present (this last check was added beyond the plan);
    • git ignores a path the scan keeps;
    • git fails, takes more than 10 s, or prints more than 64 MiB.
  • How git runs: with --no-optional-locks, so it never rewrites .git/index. GIT_DIR and its siblings are stripped. Paths are pinned to repo-root coordinates (--no-relative, --full-name) and rebased by --show-prefix.

Scan membership (crates/codegraph-extract/src/engine.rs)

The walk's per-entry decision moves into ScanRules (prunes_by_name, decide), which both the walk and the new scan_membership use. Single-path membership therefore cannot drift from the walk. Every symlink component returns Unknown, which declines.

Measured (code at 742bcb4, release build, warm cache, 5 runs after a warm-up, codegraph status . --json, generated 40,000-file TypeScript repo, ext4, 32-CPU Linux)

Scenario Fast path (median, min–max) Full inventory
Clean 212 ms (208–215) 266 ms (264–266)
18 committed edits 211 ms (206–219) 263 ms (262–264)

Both columns reported identical pending changes in every run. The final head differs from the measured 742bcb4 only by filesystem existence checks for .gitattributes above a subdirectory project; no git process is added.

Verification

  • crates/codegraph-watch/tests/git_pending_fast_path.rs (25 tests, real git, run with GIT_ATTR_NOSYSTEM and an empty XDG_CONFIG_HOME), plus a unit test for a system attributes file through a stand-in git: every case compares with pending_full_inventory and asserts which source answered.
    • Equality cases:
      • edits, an edit committed (#1829), a commit reverted, and a dirty-indexed file restored;
      • untracked add, edit and delete, a tracked delete, and a new file committed;
      • an amend, and an incremental sync followed by a commit;
      • .cache (scanned but not watched), and a project in a work-tree subdirectory.
    • Decline cases:
      • no git, an empty repo, and no record;
      • a gc'd stamp and a scope change;
      • a nested repo, assume-unchanged, and a submodule with ignore = all;
      • an ignored-but-included file, a nested .gitignore, and symlinks;
      • a hanging git (unix);
      • core.autocrlf with a revert, .gitattributes, and core.ignorecase;
      • a custom CODEGRAPH_DIR index root, and a submodule below a subdirectory project.
    • Writer cases:
      • the record is written by a full build and replaced by a full sync;
      • a file mutated after begin and restored stays dirty;
      • a file removed or excluded during the scan and restored stays dirty;
      • a full sync forgets the record before it scans;
      • a full sync that dies before its record leaves none;
      • an incremental sync that dies before any row moves keeps its paths recorded.
  • Each guard is load-bearing. With each mechanism disabled in place, its tests fail:
    • fast path off: 10 fail;
    • race guards off: 2 fail;
    • ignored-path guard off: 2 fail;
    • hidden-flags guard off: 1 fails;
    • conversion guard off: 2 fail;
    • no forget and no incremental extension: both interruption tests fail.
  • crates/codegraph-extract/tests/scan_membership.rs: for every file of a tree mixing .gitignore negation, ignore_dirs, the JVM build exception, reserved and nested .codegraph, .git, include and exclude, scan_membership agrees with scan_project. It also checks directories, missing paths and symlinks.
  • Goldens: all 19 re-indexable corpora are byte-identical. They are re-indexed outside git, so they get no record.
  • Pre-push gate (make pre-ci): see the PR checks; CI runs the full matrix, including macOS.

BEGIN_COMMIT_OVERRIDE
feat(status): answer pending changes from git's candidates when git sees every change (#1878)
END_COMMIT_OVERRIDE

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e2a1fa48e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let capture = Self {
repo,
status,
submodules: flags.submodules,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve hidden-index state in the capture

When a source is marked assume-unchanged or skip-worktree during a full build, begin detects flags.hidden_changes but this initializer discards it. If the build indexes hidden working-tree content, then the user clears the flag and restores the file to the clean HEAD version, the later flag check passes, both Git candidate queries are empty, and the file was never added to dirty; status consequently reports no pending change even though the full inventory reports the file as modified. Persist this state to force a fallback, or add the hidden paths to the recorded dirty set.

Useful? React with 👍 / 👎.

}
stored_paths.insert(file.path);
}
let fresh = scan_project(root, options)?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fall back when the post-build verification scan fails

If the source tree changes during this second scan—for example, a build process removes a directory after the initial indexing scan—scan_project can return an I/O error here, which propagates out of record. Because every full index and full sync calls this after writing all graph rows, optional fast-path bookkeeping can now fail an otherwise completed indexing operation and prevent finalization. Treat a failed verification scan like the already-tolerated Git failures: omit/delete the metadata record and let later status calls use the full inventory.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.78322% with 23 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/codegraph-watch/src/git_pending.rs 96.66% 17 Missing ⚠️
crates/codegraph-extract/src/engine.rs 97.16% 4 Missing ⚠️
crates/codegraph-watch/src/migrate.rs 88.88% 1 Missing ⚠️
crates/codegraph-watch/src/sync.rs 97.82% 1 Missing ⚠️

Impacted file tree graph

@@           Coverage Diff            @@
##             main     #289    +/-   ##
========================================
  Coverage   95.28%   95.29%            
========================================
  Files         160      161     +1     
  Lines       98419    99084   +665     
========================================
+ Hits        93781    94423   +642     
- Misses       4638     4661    +23     
Files with missing lines Coverage Δ
crates/codegraph-cli/src/main.rs 90.92% <100.00%> (+<0.01%) ⬆️
crates/codegraph-extract/src/ext_config.rs 98.01% <100.00%> (+0.10%) ⬆️
crates/codegraph-watch/src/lib.rs 97.72% <ø> (ø)
crates/codegraph-watch/src/migrate.rs 83.48% <88.88%> (+0.48%) ⬆️
crates/codegraph-watch/src/sync.rs 97.89% <97.82%> (-0.01%) ⬇️
crates/codegraph-extract/src/engine.rs 96.43% <97.16%> (+0.01%) ⬆️
crates/codegraph-watch/src/git_pending.rs 96.66% <96.66%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sunerpy
sunerpy merged commit a572fb5 into main Oct 1, 2026
10 checks passed
@sunerpy
sunerpy deleted the sync/v1.6.1-pending-fast-path branch October 1, 2026 10:43
sunerpy added a commit that referenced this pull request Oct 1, 2026
Current alignment now says #286, #288 and #289 shipped in codegraph-rs v0.52.0, closing the v0.51.0 exceptions #770 and #1829/#1878; a new dated entry records the release run, digest and black-box acceptance.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant