git: refresh index stat cache after CLI status
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))?;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user