git: refresh index stat cache after CLI status

This commit is contained in:
2026-11-07 02:39:08 +00:00
parent 9d11ab381e
commit e1cfaa93bf
4 changed files with 47 additions and 4 deletions
+5
View File
@@ -92,6 +92,11 @@ The trailer follows a blank line after the message body.
Without this variable, `commit` preserves the normal message.
The tests compare both forms with native Git, including commit IDs.
`clean -fdq` preserves tracked files, ignored files, and nested repositories.
CLI `status` refreshes cached file stats in the index after the content comparison.
Repeated status calls reuse those stats instead of hashing unchanged files again.
If the index cannot accept writes, status still reports changes.
The read-only status API does not refresh the index.
The comparison suite checks cache refresh, cache reuse, and same-size content edits.
The log applies its path filter before its count limit.
Stashes use ordinary Git commits and the `refs/stash` reflog.
+17 -3
View File
@@ -1,4 +1,4 @@
//! Read-only snapshots and patches. Ordinary Git state only; never refresh/write the index.
//! Snapshots and patches. Only CLI status refreshes the ordinary Git index stat cache.
use crate::Result;
use crate::{
HeadInfo, head_info,
@@ -223,6 +223,13 @@ fn changes(old: &Snapshot, new: &Snapshot) -> Vec<Change> {
.collect()
}
pub fn status(repo: &gix::Repository) -> Result<Status> {
status_inner(repo, false)
}
/// CLI status refreshes cached file stats. RPC status remains read-only.
pub fn refresh_status(repo: &gix::Repository) -> Result<Status> {
status_inner(repo, true)
}
fn status_inner(repo: &gix::Repository, refresh: bool) -> Result<Status> {
repo::worktree(repo)?;
let (index, conflicts) = index(repo)?;
let mut head = tree(repo, "HEAD")?;
@@ -237,13 +244,13 @@ pub fn status(repo: &gix::Repository) -> Result<Status> {
conflicted: conflicts.iter().map(|p| display(p)).collect(),
};
use gix::status::index_worktree::{Item, iter::Summary};
let iter = repo
let mut iter = repo
.status(gix::progress::Discard)
.map_err(|e| error("status", e))?
.untracked_files(gix::status::UntrackedFiles::Files)
.into_index_worktree_iter(Vec::<gix::bstr::BString>::new())
.map_err(|e| error("status", e))?;
for item in iter {
for item in iter.by_ref() {
let item = item.map_err(|e| error("status", e))?;
#[cfg(target_os = "wasi")]
if matches!(
@@ -285,6 +292,13 @@ pub fn status(repo: &gix::Repository) -> Result<Status> {
},
});
}
if refresh
&& let Some(mut outcome) = iter.into_outcome()
&& outcome.has_changes()
{
// A read-only repository must still support status, like native Git.
let _ = outcome.write_changes();
}
result.unstaged.sort_by(|a, b| a.path.cmp(&b.path));
result.untracked.sort();
Ok(result)
+1 -1
View File
@@ -808,7 +808,7 @@ pub fn run(cli: Cli) -> Result<i32> {
porcelain,
branch,
} => {
let mut status = changes::status(&repository)?;
let mut status = changes::refresh_status(&repository)?;
let index = repository
.index_or_empty()
.map_err(|e| error("status index", e))?;
+24
View File
@@ -384,6 +384,30 @@ def main [component: path, server: path = '/tmp/p1-git/server-target/debug/git-t
$executable_ok = ($executable_ok and $indexed.exit_code == 0 and ($indexed.stdout | str starts-with '100755 '))
print $'executable checkout stays clean: (if $executable_ok {"PASS"} else {"FAIL"})'
if not $executable_ok { $failures += 1 }
# Status refreshes stale stats once, then detects a same-size content change.
let cached = ($root | path join cached)
mkdir $cached
^git -C $cached init -q -b main
'base' | save ($cached | path join file.txt)
^git -C $cached add file.txt
^git -C $cached commit --no-gpg-sign -q -m cached
^touch -m -d @1700000000 ($cached | path join file.txt)
let index = ($cached | path join .git/index)
let before = (open --raw $index | hash sha256)
mut cached_ok = true
mut refreshed = ''
for pass in [1 2] {
let result = (^wasmtime run -S http=y -S cli-exit-with-code=y --dir $'($cached)::/cached' $component -C /cached status --short | complete)
let current = (open --raw $index | hash sha256)
$cached_ok = ($cached_ok and $result.exit_code == 0 and $result.stdout == '')
if $pass == 1 { $cached_ok = ($cached_ok and $current != $before); $refreshed = $current } else { $cached_ok = ($cached_ok and $current == $refreshed) }
}
'edit' | save --force ($cached | path join file.txt)
let changed = (^wasmtime run -S http=y -S cli-exit-with-code=y --dir $'($cached)::/cached' $component -C /cached status --short | complete)
let native_changed = (^git -C $cached status --short | complete)
$cached_ok = ($cached_ok and $changed.exit_code == 0 and $changed.stdout == $native_changed.stdout and $changed.stdout == " M file.txt\n")
print $'status caches stats and detects edits: (if $cached_ok {"PASS"} else {"FAIL"})'
if not $cached_ok { $failures += 1 }
# A merge tree that differs from both parents requires a combined diff.
'combined resolution\n' | save --force ($root | path join native/repo/file.txt)
let native = ($root | path join native/repo)