Conversation
Automatic sparse-index cone management triggered a full projection rebuild after every widen and narrow, costing about 2.5 seconds on a large enlistment. That rebuild produces a byte-identical projection, so it was pure overhead. The call was justified by a comment stating that the collapse runs with hooks disabled and therefore produces no PostIndexChanged notification. That premise is wrong. ForceCollapseSparseIndex is the one index-rewrite command that deliberately leaves core.hookspath intact, so git runs its own post-index-change hook for it. update-index sets updated_skipworktree unconditionally, so the mount is already notified with (updatedWorkingDirectory: false, updatedSkipWorktreeBits: true) - the exact inverse of what the manual call requested on both flags. A cone change alters only how the index represents a directory: one sparse-directory entry versus individual file entries. SparseDirectoryExpander normalizes both forms back to the full HEAD tree, so the projected set cannot change. Verified by dumping the full projected set before and after a widen on a large enlistment: 2,628,724 entries, identical SHA-256, including every blob SHA and every folder inclusion flag. Moving HEAD was used as a negative control and did change the dump, so the comparison is not vacuous. After this change a widen performs no projection rebuild. Git's own notification drives a 4 ms modified-paths validation instead. RequestIndexProjectionUpdate existed only for this call site and is removed with it. Assisted-by: Claude Opus 5 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Removes the projection rebuild that automatic sparse-index cone management triggered after every cone change. On a large enlistment that rebuild cost about 2.5 seconds per widen. It produces a byte-identical projection, so it was pure overhead.
Why the call was there, and why that reason was wrong
The call at
AutoSparseIndexConeManager.ApplyConewas justified by this comment:That premise is false. Of the three index-rewrite commands, only
ForceCollapseSparseIndexomitscore.hookspath=:core.hookspath=ForceExpandSparseIndexCollapseSparseIndexAtCloneForceCollapseSparseIndexIts own remark says so — it must keep the hooks path intact, because git consults the virtualfilesystem hook while writing the index. Only
usePreCommandHook: falseis set, which exportsCOMMAND_HOOK_LOCK=trueand affects the pre-command hook.So git does run its post-index-change hook.
update-indexsetsupdated_skipworktreeunconditionally, so the mount was already being notified with:which is the exact inverse, on both flags, of what the manual call requested.
Why the projection cannot change
A cone change alters only how the index represents a directory — one sparse-directory entry versus individual file entries.
SparseDirectoryExpandernormalizes both back to the full HEAD tree, so the projected set is invariant.Verified by dumping the full projected set (one line per entry: file path + blob SHA, folder path + inclusion flag) after each rebuild:
0536687E…96BFDB130536687E…96BFDB13Byte-identical. The folder inclusion flags are the direct test of the skip-worktree concern: had a widen changed any entry's projected-ness, those lines would differ.
Negative control: moving HEAD did change the dump, so the comparison is not vacuous.
The operation's own invalidation is untouched
Widen and narrow run inside the pre-command hook of an operation that may itself change skip-worktree. Only one of the three mechanisms is removed:
A path checkout that clears skip-worktree still reports
updated_skipworktree=1and still invalidates.Verification
InvalidateProjection: 0, zero projection rebuilds. Git's notification drives aAddMissingModifiedFilesAndRemoveThemFromPlaceholderListpass in 4 ms instead.statusclean.RequestIndexProjectionUpdateexisted only for this call site and is removed with it.Notes
reset --hard, a clean tracked file keeps an individual index entry with skip-worktree cleared but is no longer inModifiedPaths, so it drops out of the projection. Latent rather than visible, because the file stays hydrated on disk.