Synced from monorepo

Synced from monorepo

Changes:
- Temporarily disable session share link creation in the TUI
- Do not approve plan on empty Enter from the revise prompt
- Expose chat product Skills via ACP available_commands_update
- Return immediately from a blocking wait on an already-completed ACP task
- Split headless pager module for clearer structure
- Stop git worktree prune from removing user registrations on resume
- Use compaction sampler tokenizer for item token counts
- Opt-in extra root CAs via GROK_EXTRA_CA_BUNDLE
- Cancel all session subagents when the user stops
- Let the session persistence actor exit when its session ends
- Make fullscreen terminal resize much cheaper on long sessions
- Report honestly from kill_task when an ACP task does not exist
- Hide /usage for external-auth deployments
- Forward the history-load trailer’s computer_reason to the client
- Remove ineffective no-op tool reminder
- Declare slash-command screen-mode support in one place
- Keep settings enum picker on the committed value until Enter
- Reap a PTY’s full process tree
- Stream tool calls from headless mode over ACP
- Bridge gateway task lifecycle to ACP for chat session background tasks
- Don’t warn about truncated history on a suppressed replay
- Fit full-replace summarizer input and recover on context-length errors
- Stop dropping agents over an unrecognized frontmatter color
- Add /undo as a slash alias for /rewind
- Harden sleep/wake token-refresh paths against forced re-login
- Add session/list ACP method
- Give each sampling backend its own conversion module
- Treat an unenrolled child process as a lint error
- Suppress the cancelled marker on send-now wake turns
- Stop tearing down Roslyn on every edit, and read C# diagnostics

Source-Revision: 2a28b4a86cfc4a4c133c35b7fc2a6a9964387c39
This commit is contained in:
grokkybara[bot] 2026-07-30 19:07:40 +00:00
commit dd04f397b1
367 changed files with 29489 additions and 10051 deletions

View file

@ -1975,6 +1975,7 @@ pub mod gc {
#[test]
fn is_pid_alive_false_for_reaped_child() {
// A fully reaped child's pid is gone (ESRCH) and must read as dead.
#[allow(clippy::disallowed_methods)] // test fixture; the test reaps it
let mut child = std::process::Command::new("true")
.spawn()
.expect("spawn `true`");

View file

@ -1,7 +1,7 @@
//! Throttled automatic worktree GC (feature `metadata`).
use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use anyhow::Result;
@ -9,7 +9,6 @@ use crate::CleanupReport;
use crate::api::gc::{GcOptions, GcReport, age_path_enabled, gc_worktrees};
use crate::db::{ListFilter, WorktreeDb, WorktreeKind, now_epoch_secs, resolve_grok_home};
use crate::discovery::{RebuildReport, rebuild_worktree_db};
use crate::git::checkout::git_command;
pub const META_LAST_AUTO_GC_AT: &str = "last_auto_gc_at";
/// Independent throttle stamp for optional DB rebuild (not shared with GC).
@ -58,7 +57,7 @@ pub struct WorktreeAutoGcLayer {
pub dry_run: Option<bool>,
pub include_orphan_snapshots: Option<bool>,
pub max_age_by_kind: BTreeMap<WorktreeKind, Option<u64>>,
/// Optional discovery rebuild + stale `.git/worktrees/` prune (default off).
/// Optional discovery rebuild + grok-scoped stale `.git/worktrees/` scrub (default off).
pub include_rebuild: Option<bool>,
/// Independent rebuild throttle; absent ⇒ 24h.
pub rebuild_min_interval_secs: Option<u64>,
@ -459,7 +458,7 @@ pub fn maybe_auto_gc(db: &WorktreeDb, auto_opts: &AutoGcOptions) -> Result<AutoG
let (overlay, btrfs) = run_orphan_cleaners(dry_run, auto_opts.include_orphan_snapshots);
// Prune each full pass when opted in (cheap vs discovery; not rebuild-throttled).
// Scrub each full pass when opted in (cheap vs discovery; not rebuild-throttled).
let stale_registrations_cleaned = if include_rebuild && !dry_run {
prune_stale_git_worktree_registrations(&prune_repos)
} else {
@ -633,65 +632,29 @@ fn collect_source_repos_for_prune(db: &WorktreeDb) -> BTreeSet<PathBuf> {
.collect()
}
/// Scrub stale grok-owned registrations from each known source repo,
/// scoped to worktrees under the grok home to prove ownership (see
/// [`crate::git::remove_stale_worktree_registrations`] for why a blanket
/// `git worktree prune` is unsafe here).
fn prune_stale_git_worktree_registrations(repos: &BTreeSet<PathBuf>) -> u64 {
let Ok(grok_home) = resolve_grok_home() else {
tracing::warn!("auto worktree registration scrub skipped: grok home unresolved");
return 0;
};
let cleaned: u64 = repos
.iter()
.filter(|repo| repo.is_dir())
.map(|repo| prune_stale_registrations_in_repo(repo))
.map(|repo| crate::git::remove_stale_worktree_registrations_under(repo, &grok_home))
.fold(0u64, u64::saturating_add);
if cleaned > 0 {
tracing::info!(
stale_registrations_cleaned = cleaned,
"auto worktree stale git registrations pruned"
"auto worktree stale git registrations scrubbed"
);
}
cleaned
}
fn count_git_worktree_registrations(git_worktrees_dir: &Path) -> u64 {
let Ok(entries) = std::fs::read_dir(git_worktrees_dir) else {
return 0;
};
entries
.filter_map(Result::ok)
.filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false))
.count() as u64
}
fn prune_stale_registrations_in_repo(source_repo: &Path) -> u64 {
let git_worktrees = source_repo.join(".git").join("worktrees");
let before = count_git_worktree_registrations(&git_worktrees);
let output = git_command()
.args(["worktree", "prune"])
.current_dir(source_repo)
.output();
match output {
Ok(o) if o.status.success() => {
let after = count_git_worktree_registrations(&git_worktrees);
before.saturating_sub(after)
}
Ok(o) => {
tracing::warn!(
source_repo = %source_repo.display(),
status = %o.status,
stderr = %String::from_utf8_lossy(&o.stderr),
"git worktree prune failed"
);
0
}
Err(e) => {
tracing::warn!(
source_repo = %source_repo.display(),
error = %e,
"git worktree prune failed to spawn"
);
0
}
}
}
fn run_orphan_cleaners(
dry_run: bool,
include_orphan_snapshots: bool,
@ -735,6 +698,7 @@ pub fn maybe_auto_gc_default() -> Result<AutoGcReport> {
mod tests {
use super::*;
use crate::db::{WorktreeRecord, WorktreeStatus};
use std::path::Path;
use std::sync::{Mutex, MutexGuard};
static ENV_LOCK: Mutex<()> = Mutex::new(());
@ -2095,6 +2059,48 @@ mod tests {
assert!(count_regs(&repo) < before);
}
#[test]
fn prune_keeps_foreign_registrations_outside_grok_home() {
let _g = env_guard();
clear_auto_gc_env();
let fx = crate::db::GrokHomeFixture::new();
let db = WorktreeDb::open(&fx.home).unwrap();
let repo = fx.home.join("foreign-src");
let user_dir = tempfile::TempDir::new().unwrap();
let user_wt = user_dir.path().join("user-wt");
plant_stale_git_worktree(&repo, &user_wt);
let before = count_regs(&repo);
assert!(before >= 1);
let tracked = fx.home.join("foreign-tracked");
std::fs::create_dir_all(&tracked).unwrap();
let mut rec = make_rec("fk-t", tracked, WorktreeKind::Session, now_epoch_secs());
rec.source_repo = repo.clone();
db.register(&rec).unwrap();
let report = maybe_auto_gc(
&db,
&AutoGcOptions {
min_interval_secs: 0,
include_orphan_snapshots: false,
include_rebuild: true,
rebuild_min_interval_secs: 0,
..AutoGcOptions::default()
},
)
.unwrap();
assert_eq!(
report.stale_registrations_cleaned, 0,
"foreign registrations must never be scrubbed"
);
assert_eq!(
count_regs(&repo),
before,
"user registrations outside grok home must survive"
);
}
#[test]
fn rebuild_unparseable_stamp_fails_open() {
let _g = env_guard();

View file

@ -445,10 +445,6 @@ fn rehydrate_worktree_from_ref_inner(
) -> Result<WorktreeReport> {
let dest_str = dest.to_string_lossy();
// A previously-disposed worktree can leave a stale registration for this
// path; prune it so re-adding the original `subagent-<id>` dir succeeds.
snapshot_git(source_repo, &["worktree", "prune"], &[])?;
// The snapshot's first parent is the original base. Resolve it, then confirm
// the object is actually present — a parent-repo `git reset --hard` + gc can
// leave the parent pointer dangling, which `rev-parse` alone would not catch.
@ -478,11 +474,14 @@ fn rehydrate_worktree_from_ref_inner(
// A prior rehydrate may have failed after `worktree add` and left a partial dir; remove it so this attempt starts clean (worktree add fails on an existing path).
if dest.exists() {
let _ = crate::remove_worktree(dest);
let _ = snapshot_git(source_repo, &["worktree", "prune"], &[]);
if dest.exists() {
let _ = std::fs::remove_dir_all(dest);
}
}
// `git worktree add` refuses a path another registration still claims,
// so scrub `dest`'s stale registration (previously-disposed worktree, or
// the raw `remove_dir_all` fallback above) before adding.
crate::git::remove_stale_worktree_registration(source_repo, dest);
snapshot_git(
source_repo,
&[
@ -509,7 +508,7 @@ fn rehydrate_worktree_from_ref_inner(
Err(e) => {
// Best-effort cleanup; preserve the original error.
let _ = crate::remove_worktree(dest);
let _ = snapshot_git(source_repo, &["worktree", "prune"], &[]);
crate::git::remove_stale_worktree_registration(source_repo, dest);
return Err(e);
}
};
@ -1121,6 +1120,50 @@ mod tests {
);
}
/// Rehydrate must clear its own stale registration (so re-adding the
/// same path succeeds) while leaving every other entry alone — its
/// cleanup once pruned repo-wide and destroyed user registrations whose
/// paths were not visible from the container mount namespace.
#[test]
fn test_rehydrate_clears_only_its_own_stale_registration() {
xai_test_utils::require_git!();
let temp = TempDir::new().unwrap();
let (repo_path, wt) = repo_with_worktree(&temp);
std::fs::write(wt.join("tracked.txt"), "edited").unwrap();
let snap = snapshot_worktree_to_ref(&wt, "refs/grok/snapshots/stalereg", "stale").unwrap();
crate::remove_worktree(&wt).unwrap();
rehydrate_worktree_from_ref(&wt, &repo_path, &snap, None).unwrap();
let registration = repo_path
.join(".git")
.join("worktrees")
.join(wt.file_name().unwrap());
assert!(registration.is_dir(), "linked registration expected");
let user_wt = temp.path().join("user-wt");
git_capture_in(
&repo_path,
&["worktree", "add", "--detach", user_wt.to_str().unwrap()],
&[],
)
.unwrap();
std::fs::rename(&user_wt, temp.path().join("user-wt-hidden")).unwrap();
std::fs::remove_dir_all(&wt).unwrap();
let report = rehydrate_worktree_from_ref(&wt, &repo_path, &snap, None).unwrap();
assert_eq!(report.worktree_path, wt);
assert_eq!(
std::fs::read_to_string(wt.join("tracked.txt")).unwrap(),
"edited"
);
assert!(
repo_path.join(".git/worktrees/user-wt").exists(),
"user registration must survive rehydrate even when its path is hidden"
);
}
#[test]
fn test_transfer_snapshot_to_repo_makes_standalone_ref_durable() {
xai_test_utils::require_git!();

View file

@ -18,3 +18,7 @@ pub(crate) use discovery::{find_worktree_root, get_head_commit};
pub(crate) use index::{copy_git_index, update_index_stats};
pub(crate) use status::get_modified_files;
pub(crate) use worktree::worktree_add_no_checkout;
pub use worktree::{
StaleWorktreeMatch, remove_stale_worktree_registration, remove_stale_worktree_registrations,
remove_stale_worktree_registrations_under,
};

View file

@ -1,6 +1,6 @@
//! Git worktree operations.
use std::path::Path;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
@ -28,3 +28,372 @@ pub(crate) fn worktree_add_no_checkout(source: &Path, dest: &str, git_ref: &str)
Ok(())
}
/// Which stale registrations [`remove_stale_worktree_registrations`] removes.
#[derive(Clone, Copy, Debug)]
pub enum StaleWorktreeMatch<'a> {
/// Exactly the registration whose recorded worktree path is this path.
Path(&'a Path),
/// Every registration whose recorded worktree path is under this prefix
/// (e.g. a tool-owned base directory, proving ownership of the entries).
UnderPrefix(&'a Path),
}
/// Remove stale `.git/worktrees/<id>` registrations matching `match_rule`.
///
/// Deliberately not `git worktree prune`: prune deletes every registration
/// whose worktree path is not visible from the current mount namespace (git
/// applies no expiry protection to that case) and deletes `.git/worktrees`
/// itself once emptied — under a container that does not mount the user's
/// linked worktrees, that wiped them all. Best-effort: failures are logged,
/// never returned. Returns the number of registrations removed (git suffixes
/// ids on basename collisions, so an id may differ from the basename).
pub fn remove_stale_worktree_registrations(
source_repo: &Path,
match_rule: StaleWorktreeMatch<'_>,
) -> u64 {
if let StaleWorktreeMatch::Path(p) = match_rule
&& p.exists()
{
return 0;
}
let common_dir = match git_command()
.current_dir(source_repo)
.args(["rev-parse", "--git-common-dir"])
.output()
{
Ok(o) if o.status.success() => {
let path = PathBuf::from(String::from_utf8_lossy(&o.stdout).trim());
if path.is_absolute() {
path
} else {
source_repo.join(path)
}
}
Ok(o) => {
tracing::warn!(
source_repo = %source_repo.display(),
stderr = %String::from_utf8_lossy(&o.stderr),
"stale registration scrub skipped: git rev-parse --git-common-dir failed"
);
return 0;
}
Err(e) => {
tracing::warn!(
source_repo = %source_repo.display(),
error = %e,
"stale registration scrub skipped: git failed to spawn"
);
return 0;
}
};
let Ok(entries) = std::fs::read_dir(common_dir.join("worktrees")) else {
return 0;
};
let normalized_target = match match_rule {
StaleWorktreeMatch::Path(p) | StaleWorktreeMatch::UnderPrefix(p) => normalized_for_match(p),
};
let mut removed = 0u64;
for entry in entries.flatten() {
let registration = entry.path();
if !registration.is_dir() || registration.join("locked").exists() {
continue;
}
let Ok(backlink) = std::fs::read_to_string(registration.join("gitdir")) else {
continue;
};
// The backlink names `<worktree>/.git`; under
// `worktree.useRelativePaths` (git >= 2.48) it is relative to the
// registration dir, not the CWD.
let backlink_path = Path::new(backlink.trim());
let backlink_abs = if backlink_path.is_relative() {
registration.join(backlink_path)
} else {
backlink_path.to_path_buf()
};
let Some(recorded) = backlink_abs.parent() else {
continue;
};
if recorded.exists() {
continue;
}
let recorded = normalized_for_match(recorded);
let matched = match match_rule {
StaleWorktreeMatch::Path(_) => recorded == normalized_target,
StaleWorktreeMatch::UnderPrefix(_) => recorded.starts_with(&normalized_target),
};
if !matched {
continue;
}
match std::fs::remove_dir_all(&registration) {
Ok(()) => {
tracing::debug!(
registration = %registration.display(),
worktree = %recorded.display(),
"removed stale worktree registration"
);
removed += 1;
}
Err(e) => {
tracing::warn!(
registration = %registration.display(),
error = %e,
"failed to remove stale worktree registration"
);
}
}
}
removed
}
/// [`remove_stale_worktree_registrations`] scoped to exactly one worktree path.
pub fn remove_stale_worktree_registration(source_repo: &Path, worktree_path: &Path) -> u64 {
remove_stale_worktree_registrations(source_repo, StaleWorktreeMatch::Path(worktree_path))
}
/// [`remove_stale_worktree_registrations`] scoped to a tool-owned base directory.
pub fn remove_stale_worktree_registrations_under(source_repo: &Path, prefix: &Path) -> u64 {
remove_stale_worktree_registrations(source_repo, StaleWorktreeMatch::UnderPrefix(prefix))
}
/// Canonicalize the deepest existing ancestor and re-append the missing
/// tail: git records the realpath at `worktree add` time, so a symlinked
/// spelling must compare equal even after the path itself is deleted.
fn normalized_for_match(path: &Path) -> PathBuf {
let mut missing = Vec::new();
let mut cursor = path;
loop {
if let Ok(canonical) = dunce::canonicalize(cursor) {
let mut result = canonical;
for component in missing.iter().rev() {
result.push(component);
}
return result;
}
match (cursor.parent(), cursor.file_name()) {
(Some(parent), Some(name)) => {
missing.push(name.to_os_string());
cursor = parent;
}
_ => return path.to_path_buf(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn run_git(cwd: &Path, args: &[&str]) {
let out = std::process::Command::new("git")
.args(args)
.current_dir(cwd)
.output()
.expect("run git");
assert!(
out.status.success(),
"git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
}
fn init_repo_with_worktrees() -> (tempfile::TempDir, PathBuf) {
let tmp = tempfile::TempDir::new().unwrap();
let repo = tmp.path().join("repo");
std::fs::create_dir(&repo).unwrap();
run_git(&repo, &["init"]);
run_git(&repo, &["config", "user.email", "t@test"]);
run_git(&repo, &["config", "user.name", "t"]);
std::fs::write(repo.join("f.txt"), b"x").unwrap();
run_git(&repo, &["add", "f.txt"]);
run_git(&repo, &["commit", "-m", "init"]);
(tmp, repo)
}
fn add_worktree(repo: &Path, wt: &Path) {
run_git(
repo,
&["worktree", "add", "--detach", wt.to_str().unwrap(), "HEAD"],
);
}
#[test]
fn removes_only_the_matching_stale_registration() {
let (tmp, repo) = init_repo_with_worktrees();
let git_worktrees = repo.join(".git").join("worktrees");
let target = tmp.path().join("target-wt");
add_worktree(&repo, &target);
std::fs::remove_dir_all(&target).unwrap();
let hidden = tmp.path().join("hidden-wt");
add_worktree(&repo, &hidden);
std::fs::rename(&hidden, tmp.path().join("hidden-wt-moved")).unwrap();
std::fs::create_dir(git_worktrees.join("bare-entry")).unwrap();
let removed = remove_stale_worktree_registration(&repo, &target);
assert_eq!(removed, 1);
assert!(!git_worktrees.join("target-wt").exists());
assert!(
git_worktrees.join("hidden-wt").exists(),
"non-matching registration must survive even when its path is gone"
);
assert!(git_worktrees.join("bare-entry").exists());
assert!(git_worktrees.exists());
}
/// Rewrite a registration's `gitdir` backlink to the relative layout
/// `worktree.useRelativePaths` (git >= 2.48) produces, without requiring
/// that git version on the test host.
fn make_backlink_relative(repo: &Path, reg_name: &str, worktree: &Path) {
let reg_dir = repo.join(".git").join("worktrees").join(reg_name);
let target = worktree.join(".git");
let mut ups = PathBuf::new();
let mut cursor = reg_dir.as_path();
loop {
if let Ok(rest) = target.strip_prefix(cursor) {
std::fs::write(
reg_dir.join("gitdir"),
format!("{}\n", ups.join(rest).display()),
)
.unwrap();
return;
}
cursor = cursor.parent().expect("shared ancestor");
ups.push("..");
}
}
#[test]
fn resolves_relative_backlink_against_registration_dir() {
let (tmp, repo) = init_repo_with_worktrees();
let stale = tmp.path().join("rel-stale");
add_worktree(&repo, &stale);
make_backlink_relative(&repo, "rel-stale", &stale);
std::fs::remove_dir_all(&stale).unwrap();
let live = tmp.path().join("rel-live");
add_worktree(&repo, &live);
make_backlink_relative(&repo, "rel-live", &live);
let removed_live = remove_stale_worktree_registrations_under(&repo, tmp.path());
assert_eq!(removed_live, 1, "only the stale relative entry is removed");
assert!(!repo.join(".git/worktrees/rel-stale").exists());
assert!(
repo.join(".git/worktrees/rel-live").exists(),
"live worktree with relative backlink must survive"
);
}
#[test]
fn keeps_registration_when_worktree_still_exists() {
let (tmp, repo) = init_repo_with_worktrees();
let wt = tmp.path().join("live-wt");
add_worktree(&repo, &wt);
let removed = remove_stale_worktree_registration(&repo, &wt);
assert_eq!(removed, 0);
assert!(repo.join(".git/worktrees/live-wt").exists());
}
#[test]
fn keeps_locked_registration() {
let (tmp, repo) = init_repo_with_worktrees();
let wt = tmp.path().join("locked-wt");
add_worktree(&repo, &wt);
run_git(&repo, &["worktree", "lock", wt.to_str().unwrap()]);
std::fs::remove_dir_all(&wt).unwrap();
let removed = remove_stale_worktree_registration(&repo, &wt);
assert_eq!(removed, 0);
assert!(repo.join(".git/worktrees/locked-wt").exists());
}
#[test]
fn under_prefix_removes_only_owned_stale_registrations() {
let (tmp, repo) = init_repo_with_worktrees();
let git_worktrees = repo.join(".git").join("worktrees");
let owned_base = tmp.path().join("owned-base");
let owned_stale = owned_base.join("instance").join("wt-stale");
let owned_locked = owned_base.join("instance").join("wt-locked");
let owned_live = owned_base.join("instance").join("wt-live");
std::fs::create_dir_all(owned_stale.parent().unwrap()).unwrap();
add_worktree(&repo, &owned_stale);
add_worktree(&repo, &owned_locked);
add_worktree(&repo, &owned_live);
std::fs::remove_dir_all(&owned_stale).unwrap();
run_git(&repo, &["worktree", "lock", owned_locked.to_str().unwrap()]);
std::fs::remove_dir_all(&owned_locked).unwrap();
let foreign = tmp.path().join("foreign-wt");
add_worktree(&repo, &foreign);
std::fs::rename(&foreign, tmp.path().join("foreign-wt-moved")).unwrap();
std::fs::create_dir(git_worktrees.join("bare-entry")).unwrap();
let removed = remove_stale_worktree_registrations_under(&repo, &owned_base);
assert_eq!(removed, 1);
assert!(!git_worktrees.join("wt-stale").exists());
assert!(
git_worktrees.join("wt-locked").exists(),
"locked registration must survive even when owned and stale"
);
assert!(
git_worktrees.join("wt-live").exists(),
"live owned registration must survive"
);
assert!(
git_worktrees.join("foreign-wt").exists(),
"foreign registration must survive even when its path is hidden"
);
assert!(git_worktrees.join("bare-entry").exists());
assert!(git_worktrees.exists());
}
#[cfg(unix)]
#[test]
fn under_prefix_matches_symlinked_base_spelling() {
let (tmp, repo) = init_repo_with_worktrees();
let real_base = tmp.path().join("real-base");
std::fs::create_dir(&real_base).unwrap();
let wt = real_base.join("wt");
add_worktree(&repo, &wt);
std::fs::remove_dir_all(&wt).unwrap();
let alias_base = tmp.path().join("alias-base");
std::os::unix::fs::symlink(&real_base, &alias_base).unwrap();
let removed = remove_stale_worktree_registrations_under(&repo, &alias_base);
assert_eq!(removed, 1, "symlinked base spelling must match");
assert!(!repo.join(".git/worktrees/wt").exists());
}
#[test]
fn matches_across_symlinked_parent_spelling() {
let (tmp, repo) = init_repo_with_worktrees();
let real_parent = tmp.path().join("real-parent");
std::fs::create_dir(&real_parent).unwrap();
let wt = real_parent.join("wt");
add_worktree(&repo, &wt);
std::fs::remove_dir_all(&wt).unwrap();
#[cfg(unix)]
{
let alias = tmp.path().join("alias-parent");
std::os::unix::fs::symlink(&real_parent, &alias).unwrap();
let removed = remove_stale_worktree_registration(&repo, &alias.join("wt"));
assert_eq!(removed, 1, "symlinked spelling of the parent must match");
assert!(!repo.join(".git/worktrees/wt").exists());
}
}
}

View file

@ -63,6 +63,10 @@ pub use discovery::{RebuildReport, discover_worktrees, rebuild_worktree_db};
pub use git::checkout::{
rehydrate_worktree_from_ref, snapshot_worktree_to_ref, transfer_snapshot_to_repo,
};
pub use git::{
StaleWorktreeMatch, remove_stale_worktree_registration, remove_stale_worktree_registrations,
remove_stale_worktree_registrations_under,
};
pub use sync::{SourceDirtyState, SyncReport, WorktreeSync, collect_source_dirty_state};
#[cfg(target_os = "linux")]
pub use worktree::execute::cleanup_snapshot_git_state;

View file

@ -604,6 +604,7 @@ fn replay_staged_changes(
// <mode> SP <hex-hash> TAB <path> NUL
if !staged_adds.is_empty() {
use std::io::Write;
#[allow(clippy::disallowed_methods)] // git command, waited on below
let mut child = git_command()
.current_dir(worktree)
.args(["update-index", "-z", "--index-info"])