Skip to main content

sley_worktree/
status.rs

1//! The `git status` engine: short-status streaming/collection, tracked-only fast paths, and untracked-status rows.
2//!
3//! Split out of `lib.rs` in the wave-47 mechanical refactor: a pure code move
4//! (no function body changed); all items are re-exported from `lib.rs`.
5use super::*;
6use crate::checkout::*;
7use crate::ignore::*;
8use crate::index::*;
9use crate::index_io::*;
10use crate::types_admin::*;
11use std::sync::atomic::{AtomicUsize, Ordering};
12
13pub fn stream_short_status<F>(
14    worktree_root: impl AsRef<Path>,
15    git_dir: impl AsRef<Path>,
16    format: ObjectFormat,
17    emit: F,
18) -> Result<()>
19where
20    F: for<'a> FnMut(ShortStatusRow<'a>) -> Result<StreamControl>,
21{
22    stream_short_status_with_options(
23        worktree_root,
24        git_dir,
25        format,
26        ShortStatusOptions::default(),
27        emit,
28    )
29}
30
31pub fn short_status_count(
32    worktree_root: impl AsRef<Path>,
33    git_dir: impl AsRef<Path>,
34    format: ObjectFormat,
35) -> Result<usize> {
36    short_status_count_with_options(
37        worktree_root,
38        git_dir,
39        format,
40        ShortStatusOptions::default(),
41    )
42}
43
44pub fn short_status_count_with_options(
45    worktree_root: impl AsRef<Path>,
46    git_dir: impl AsRef<Path>,
47    format: ObjectFormat,
48    options: ShortStatusOptions,
49) -> Result<usize> {
50    let worktree_root = worktree_root.as_ref();
51    let git_dir = git_dir.as_ref();
52    let db = FileObjectDatabase::from_git_dir(git_dir, format);
53    if !options.include_ignored
54        && let Some(count) = short_status_borrowed_head_matches_index_count_if_possible(
55            worktree_root,
56            git_dir,
57            format,
58            &db,
59            options.untracked_mode,
60        )?
61    {
62        return Ok(count);
63    }
64    let mut count = 0usize;
65    stream_short_status_with_options(worktree_root, git_dir, format, options, |_| {
66        count += 1;
67        Ok(StreamControl::Continue)
68    })?;
69    Ok(count)
70}
71
72#[derive(Debug, Clone, Default)]
73pub(crate) struct StatusProfileCounters {
74    pub(crate) fast_path_borrowed: bool,
75    pub(crate) read_dir_calls: u64,
76    pub(crate) dir_entries_seen: u64,
77    pub(crate) file_type_calls: u64,
78    pub(crate) ignore_checks: u64,
79    pub(crate) ignore_pattern_tests: u64,
80    pub(crate) ignore_glob_fallback_tests: u64,
81    pub(crate) tracked_exact_hits: u64,
82    pub(crate) tracked_dir_prefix_hits: u64,
83    pub(crate) tracked_skip_worktree_prefix_hits: u64,
84    pub(crate) read_dir_entry_vec_cap_bytes: u64,
85    pub(crate) read_dir_entry_vec_max_len: u64,
86    pub(crate) read_dir_entry_vec_max_cap: u64,
87    pub(crate) read_dir_name_vec_cap_bytes: u64,
88    pub(crate) read_dir_name_vec_max_len: u64,
89    pub(crate) read_dir_name_vec_max_cap: u64,
90    pub(crate) untracked_rows: u64,
91    pub(crate) tracked_elapsed_us: u128,
92    pub(crate) untracked_elapsed_us: u128,
93    pub(crate) render_elapsed_us: u128,
94    pub(crate) overlap_enabled: bool,
95}
96
97pub(crate) const STATUS_BORROWED_OVERLAP_MIN_STAGE0: usize = 1024;
98
99#[derive(Debug, Clone, Copy)]
100pub(crate) struct StatusExecutor {
101    workers: usize,
102}
103
104impl StatusExecutor {
105    pub(crate) fn new() -> Self {
106        let workers = std::thread::available_parallelism()
107            .map(|count| count.get())
108            .unwrap_or(1)
109            .max(1);
110        Self { workers }
111    }
112
113    pub(crate) fn worker_count_for(
114        self,
115        item_count: usize,
116        min_items_per_worker: usize,
117        cap: usize,
118    ) -> usize {
119        if item_count == 0 {
120            return 0;
121        }
122        let requested = item_count.div_ceil(min_items_per_worker.max(1));
123        self.workers.min(cap.max(1)).min(requested).max(1)
124    }
125
126    pub(crate) fn spawn<'scope, 'env, F, T>(
127        self,
128        scope: &'scope std::thread::Scope<'scope, 'env>,
129        name: &str,
130        f: F,
131    ) -> Result<StatusTask<'scope, T>>
132    where
133        F: FnOnce() -> Result<T> + Send + 'scope,
134        T: Send + 'scope,
135    {
136        let handle = std::thread::Builder::new()
137            .name(name.to_string())
138            .spawn_scoped(scope, f)
139            .map_err(|err| {
140                GitError::Command(format!("failed to spawn status worker `{name}`: {err}"))
141            })?;
142        Ok(StatusTask {
143            name: name.to_string(),
144            handle,
145        })
146    }
147}
148
149pub(crate) struct StatusTask<'scope, T> {
150    name: String,
151    handle: std::thread::ScopedJoinHandle<'scope, Result<T>>,
152}
153
154impl<T> StatusTask<'_, T> {
155    pub(crate) fn join(self) -> Result<T> {
156        self.handle
157            .join()
158            .map_err(|_| GitError::Command(format!("status worker `{}` panicked", self.name)))?
159    }
160}
161
162pub(crate) enum BorrowedIndexBytes {
163    Owned(Vec<u8>),
164    Mapped(sley_mmap::MappedFile),
165}
166
167impl AsRef<[u8]> for BorrowedIndexBytes {
168    fn as_ref(&self) -> &[u8] {
169        match self {
170            Self::Owned(bytes) => bytes,
171            Self::Mapped(bytes) => bytes.as_bytes(),
172        }
173    }
174}
175
176pub(crate) fn read_borrowed_index_bytes(index_path: &Path) -> Result<BorrowedIndexBytes> {
177    match sley_mmap::MappedFile::open_index(index_path) {
178        Ok(mapped) => Ok(BorrowedIndexBytes::Mapped(mapped)),
179        Err(_) => Ok(BorrowedIndexBytes::Owned(fs::read(index_path)?)),
180    }
181}
182
183impl StatusProfileCounters {
184    fn enabled() -> bool {
185        std::env::var_os("SLEY_STATUS_PROFILE").is_some_and(|value| value != "0")
186    }
187
188    fn memory_enabled() -> bool {
189        std::env::var_os("SLEY_STATUS_PROFILE")
190            .and_then(|value| value.into_string().ok())
191            .is_some_and(|value| value == "mem" || value == "memory")
192    }
193
194    pub(crate) fn merge_untracked(&mut self, other: StatusProfileCounters) {
195        self.read_dir_calls += other.read_dir_calls;
196        self.dir_entries_seen += other.dir_entries_seen;
197        self.file_type_calls += other.file_type_calls;
198        self.ignore_checks += other.ignore_checks;
199        self.ignore_pattern_tests += other.ignore_pattern_tests;
200        self.ignore_glob_fallback_tests += other.ignore_glob_fallback_tests;
201        self.tracked_exact_hits += other.tracked_exact_hits;
202        self.tracked_dir_prefix_hits += other.tracked_dir_prefix_hits;
203        self.tracked_skip_worktree_prefix_hits += other.tracked_skip_worktree_prefix_hits;
204        self.read_dir_entry_vec_cap_bytes += other.read_dir_entry_vec_cap_bytes;
205        self.read_dir_entry_vec_max_len = self
206            .read_dir_entry_vec_max_len
207            .max(other.read_dir_entry_vec_max_len);
208        self.read_dir_entry_vec_max_cap = self
209            .read_dir_entry_vec_max_cap
210            .max(other.read_dir_entry_vec_max_cap);
211        self.read_dir_name_vec_cap_bytes += other.read_dir_name_vec_cap_bytes;
212        self.read_dir_name_vec_max_len = self
213            .read_dir_name_vec_max_len
214            .max(other.read_dir_name_vec_max_len);
215        self.read_dir_name_vec_max_cap = self
216            .read_dir_name_vec_max_cap
217            .max(other.read_dir_name_vec_max_cap);
218        self.untracked_rows += other.untracked_rows;
219        self.untracked_elapsed_us += other.untracked_elapsed_us;
220    }
221
222    fn emit(&self) {
223        eprintln!(
224            "{{\"schema\":\"sley.status.profile.v1\",\
225             \"fast_path_borrowed\":{},\
226             \"read_dir_calls\":{},\
227             \"dir_entries_seen\":{},\
228             \"file_type_calls\":{},\
229             \"ignore_checks\":{},\
230             \"ignore_pattern_tests\":{},\
231             \"ignore_glob_fallback_tests\":{},\
232             \"tracked_exact_hits\":{},\
233             \"tracked_dir_prefix_hits\":{},\
234             \"tracked_skip_worktree_prefix_hits\":{},\
235             \"read_dir_entry_size\":{},\
236             \"read_dir_entry_vec_cap_bytes\":{},\
237             \"read_dir_entry_vec_max_len\":{},\
238             \"read_dir_entry_vec_max_cap\":{},\
239             \"read_dir_name_size\":{},\
240             \"read_dir_name_vec_cap_bytes\":{},\
241             \"read_dir_name_vec_max_len\":{},\
242             \"read_dir_name_vec_max_cap\":{},\
243             \"untracked_rows\":{},\
244             \"tracked_elapsed_us\":{},\
245             \"untracked_elapsed_us\":{},\
246             \"render_elapsed_us\":{},\
247             \"overlap_enabled\":{}}}",
248            self.fast_path_borrowed,
249            self.read_dir_calls,
250            self.dir_entries_seen,
251            self.file_type_calls,
252            self.ignore_checks,
253            self.ignore_pattern_tests,
254            self.ignore_glob_fallback_tests,
255            self.tracked_exact_hits,
256            self.tracked_dir_prefix_hits,
257            self.tracked_skip_worktree_prefix_hits,
258            std::mem::size_of::<fs::DirEntry>(),
259            self.read_dir_entry_vec_cap_bytes,
260            self.read_dir_entry_vec_max_len,
261            self.read_dir_entry_vec_max_cap,
262            std::mem::size_of::<std::ffi::OsString>(),
263            self.read_dir_name_vec_cap_bytes,
264            self.read_dir_name_vec_max_len,
265            self.read_dir_name_vec_max_cap,
266            self.untracked_rows,
267            self.tracked_elapsed_us,
268            self.untracked_elapsed_us,
269            self.render_elapsed_us,
270            self.overlap_enabled
271        );
272    }
273}
274
275pub(crate) fn status_profile_rss_vsz_bytes() -> Option<(u64, u64)> {
276    let pid = std::process::id().to_string();
277    let output = Command::new("ps")
278        .args(["-o", "rss=", "-o", "vsz=", "-p", &pid])
279        .output()
280        .ok()?;
281    if !output.status.success() {
282        return None;
283    }
284    let text = String::from_utf8(output.stdout).ok()?;
285    let mut parts = text.split_whitespace();
286    let rss_kib = parts.next()?.parse::<u64>().ok()?;
287    let vsz_kib = parts.next()?.parse::<u64>().ok()?;
288    Some((rss_kib * 1024, vsz_kib * 1024))
289}
290
291pub(crate) fn status_profile_pause(label: &str) {
292    let Some(target) =
293        std::env::var_os("SLEY_STATUS_PROFILE_PAUSE_AT").and_then(|value| value.into_string().ok())
294    else {
295        return;
296    };
297    if target != label && target != "*" {
298        return;
299    }
300    let seconds = std::env::var("SLEY_STATUS_PROFILE_PAUSE_SECS")
301        .ok()
302        .and_then(|value| value.parse::<u64>().ok())
303        .unwrap_or(30);
304    eprintln!(
305        "{{\"schema\":\"sley.status.mem.pause.v1\",\"label\":\"{}\",\"pid\":{},\"seconds\":{}}}",
306        label,
307        std::process::id(),
308        seconds
309    );
310    std::thread::sleep(std::time::Duration::from_secs(seconds));
311}
312
313pub(crate) fn status_profile_mem(label: &str, details: &[(&str, usize)]) {
314    if !StatusProfileCounters::memory_enabled() {
315        return;
316    }
317    let (rss_bytes, vsz_bytes) = status_profile_rss_vsz_bytes().unwrap_or((0, 0));
318    eprint!(
319        "{{\"schema\":\"sley.status.mem.v1\",\"label\":\"{}\",\"pid\":{},\"rss_bytes\":{},\"vsz_bytes\":{}",
320        label,
321        std::process::id(),
322        rss_bytes,
323        vsz_bytes
324    );
325    for (key, value) in details {
326        eprint!(",\"{}\":{}", key, value);
327    }
328    eprintln!("}}");
329    status_profile_pause(label);
330}
331
332/// Compare one expected tracked entry to the worktree path named by `path`.
333///
334/// `path` is repository-relative and uses the platform path representation. For
335/// callers that already carry git's byte path form, use
336/// [`worktree_entry_state_by_git_path`].
337pub fn worktree_entry_state(
338    worktree_root: impl AsRef<Path>,
339    git_dir: impl AsRef<Path>,
340    format: ObjectFormat,
341    path: impl AsRef<Path>,
342    expected_oid: &ObjectId,
343    expected_mode: u32,
344    index_probe: Option<&IndexStatProbe>,
345) -> Result<WorktreeEntryState> {
346    let path = path.as_ref();
347    if path.is_absolute() {
348        return Err(GitError::InvalidPath(format!(
349            "worktree entry path {} is absolute",
350            path.display()
351        )));
352    }
353    let git_path = git_path_bytes(path)?;
354    worktree_entry_state_by_git_path(
355        worktree_root,
356        git_dir,
357        format,
358        &git_path,
359        expected_oid,
360        expected_mode,
361        index_probe,
362    )
363}
364
365/// Compare one expected tracked entry to the worktree path named by a
366/// repository-relative git path (`/` separators, raw bytes).
367///
368/// The comparison uses the same clean-filter, symlink-target, gitlink, and
369/// racy-clean stat shortcut rules as [`stream_short_status_with_options`].
370pub fn worktree_entry_state_by_git_path(
371    worktree_root: impl AsRef<Path>,
372    git_dir: impl AsRef<Path>,
373    format: ObjectFormat,
374    git_path: &[u8],
375    expected_oid: &ObjectId,
376    expected_mode: u32,
377    index_probe: Option<&IndexStatProbe>,
378) -> Result<WorktreeEntryState> {
379    let worktree_root = worktree_root.as_ref();
380    let git_dir = git_dir.as_ref();
381    let stat_cache =
382        index_probe.and_then(|probe| probe.stat_cache_for(git_path, expected_oid, expected_mode));
383    let Some(worktree_entry) = worktree_entry_for_git_path(
384        worktree_root,
385        git_dir,
386        format,
387        git_path,
388        expected_oid,
389        expected_mode,
390        stat_cache.as_ref(),
391    )?
392    else {
393        return Ok(WorktreeEntryState::Deleted);
394    };
395    if worktree_entry.mode == expected_mode && worktree_entry.oid == *expected_oid {
396        Ok(WorktreeEntryState::Clean)
397    } else {
398        Ok(WorktreeEntryState::Modified)
399    }
400}
401
402pub fn stream_short_status_with_options<F>(
403    worktree_root: impl AsRef<Path>,
404    git_dir: impl AsRef<Path>,
405    format: ObjectFormat,
406    options: ShortStatusOptions,
407    mut emit: F,
408) -> Result<()>
409where
410    F: for<'a> FnMut(ShortStatusRow<'a>) -> Result<StreamControl>,
411{
412    let worktree_root = worktree_root.as_ref();
413    let git_dir = git_dir.as_ref();
414    let db = FileObjectDatabase::from_git_dir(git_dir, format);
415    if !options.include_ignored
416        && let Some(()) = stream_short_status_borrowed_head_matches_index_if_possible(
417            worktree_root,
418            git_dir,
419            format,
420            &db,
421            options.untracked_mode,
422            &mut emit,
423        )?
424    {
425        return Ok(());
426    }
427    for entry in collect_short_status_with_options(worktree_root, git_dir, format, options)? {
428        if emit(entry.as_row())?.is_stop() {
429            break;
430        }
431    }
432    Ok(())
433}
434
435pub(crate) fn collect_short_status_with_options(
436    worktree_root: impl AsRef<Path>,
437    git_dir: impl AsRef<Path>,
438    format: ObjectFormat,
439    options: ShortStatusOptions,
440) -> Result<Vec<ShortStatusEntry>> {
441    let worktree_root = worktree_root.as_ref();
442    let git_dir = git_dir.as_ref();
443    let db = FileObjectDatabase::from_git_dir(git_dir, format);
444    if !options.include_ignored
445        && let Some(entries) = short_status_borrowed_head_matches_index_if_possible(
446            worktree_root,
447            git_dir,
448            format,
449            &db,
450            options.untracked_mode,
451        )?
452    {
453        return Ok(entries);
454    }
455    // Parse the index once: the stat cache lets the worktree walk skip
456    // re-hashing files whose stat proves they are unchanged since staging
457    // (git's racy-git shortcut). When HEAD matches the index, the status
458    // comparison can stream directly from the parsed index and avoid building a
459    // second path-sorted copy of every tracked entry.
460    let (mut parsed_index, mut stat_cache, mut head_matches_index) =
461        read_index_with_stat_cache(git_dir, format, &db)?;
462    let sparse_checkout_active = sparse_checkout_active_for_status(git_dir, &parsed_index);
463    if sparse_checkout_active && parsed_index.entries.iter().any(IndexEntry::is_sparse_dir) {
464        expand_sparse_index(&mut parsed_index, &db, format)?;
465        stat_cache = IndexStatCache::from_index_mtime(&parsed_index, stat_cache.index_mtime);
466        head_matches_index = false;
467    }
468    let mut unmerged_entries = short_status_unmerged_entries(&parsed_index);
469    let unmerged_paths = unmerged_entries
470        .iter()
471        .map(|entry| entry.path.clone())
472        .collect::<BTreeSet<_>>();
473    if head_matches_index && !options.include_ignored {
474        let mut ignores = IgnoreMatcher::from_worktree_base(worktree_root)?;
475        let entries = short_status_tracked_only(
476            worktree_root,
477            git_dir,
478            format,
479            &db,
480            &parsed_index,
481            &stat_cache,
482            true,
483            sparse_checkout_active,
484            options.untracked_mode,
485        );
486        let mut entries = entries?;
487        entries.retain(|entry| !unmerged_paths.contains(&entry.path));
488        let untracked_paths = status_untracked_paths_from_index(
489            worktree_root,
490            git_dir,
491            &parsed_index,
492            &stat_cache,
493            &mut ignores,
494            options.untracked_mode,
495            None,
496        )?;
497        for path in untracked_paths {
498            entries.push(ShortStatusEntry {
499                index: b'?',
500                worktree: b'?',
501                path,
502                head_mode: None,
503                index_mode: None,
504                worktree_mode: None,
505                head_oid: None,
506                index_oid: None,
507                submodule: None,
508            });
509        }
510        entries.append(&mut unmerged_entries);
511        entries.sort_by(|left, right| {
512            status_sort_category(left)
513                .cmp(&status_sort_category(right))
514                .then_with(|| left.path.cmp(&right.path))
515        });
516        return Ok(entries);
517    }
518    let index = index_entries_from_index(parsed_index);
519    let head = if head_matches_index {
520        None
521    } else {
522        Some(head_tree_entries(git_dir, format, &db)?)
523    };
524    let known_tracked_paths = index.keys().cloned().collect::<BTreeSet<_>>();
525    let tracked_paths = if options.untracked_mode == StatusUntrackedMode::None {
526        Some(&known_tracked_paths)
527    } else {
528        None
529    };
530    let mut ignores = IgnoreMatcher::from_worktree_base(worktree_root)?;
531    let (worktree, submodule_dirt_map, tracked_presence) =
532        status_worktree_entries_with_submodule_dirt(
533            worktree_root,
534            git_dir,
535            format,
536            &stat_cache,
537            Some(&known_tracked_paths),
538            tracked_paths,
539            Some(&mut ignores),
540        )?;
541    let mut entries = Vec::new();
542    if head_matches_index {
543        collect_status_entries_head_matches_index(
544            &index,
545            &worktree,
546            &tracked_presence,
547            &stat_cache,
548            sparse_checkout_active,
549            &submodule_dirt_map,
550            options.untracked_mode,
551            &mut entries,
552        );
553    } else if let Some(head) = head.as_ref() {
554        collect_status_entries_with_head(
555            StatusComparisonInputs {
556                head,
557                index: &index,
558                worktree: &worktree,
559                tracked_presence: &tracked_presence,
560                stat_cache: &stat_cache,
561                sparse_checkout_active,
562                submodule_dirt_map: &submodule_dirt_map,
563                ignores: &ignores,
564            },
565            options.untracked_mode,
566            &mut entries,
567        );
568    }
569    entries.retain(|entry| !unmerged_paths.contains(&entry.path));
570    entries.append(&mut unmerged_entries);
571    if options.include_ignored {
572        let ignored_directory_rows = !matches!(options.untracked_mode, StatusUntrackedMode::All);
573        let ignored_paths = ignored_untracked_paths(
574            worktree_root,
575            git_dir,
576            &index,
577            &ignores,
578            ignored_directory_rows,
579        )?;
580        let ignored_paths: Vec<Vec<u8>> = match options.ignored_mode {
581            StatusIgnoredMode::Matching => ignored_paths,
582            StatusIgnoredMode::Traditional
583                if matches!(options.untracked_mode, StatusUntrackedMode::All) =>
584            {
585                ignored_paths
586            }
587            StatusIgnoredMode::Traditional => {
588                let mut rolled = BTreeSet::new();
589                for path in ignored_paths {
590                    let path = ignored_traditional_rollup_path(
591                        worktree_root,
592                        git_dir,
593                        &path,
594                        &index,
595                        &ignores,
596                    )?;
597                    if ignored_traditional_path_is_empty_directory(worktree_root, &path)? {
598                        continue;
599                    }
600                    rolled.insert(path);
601                }
602                rolled.into_iter().collect()
603            }
604        };
605        for path in ignored_paths {
606            entries.push(ShortStatusEntry {
607                index: b'!',
608                worktree: b'!',
609                path,
610                head_mode: None,
611                index_mode: None,
612                worktree_mode: None,
613                head_oid: None,
614                index_oid: None,
615                submodule: None,
616            });
617        }
618    }
619    let untracked_paths: Vec<Vec<u8>> = match options.untracked_mode {
620        StatusUntrackedMode::All => worktree
621            .iter()
622            .filter_map(|(path, entry)| {
623                let is_directory = entry.mode == 0o040000 && entry.oid.is_null();
624                if index.contains_key(path)
625                    || path_or_parent_is_ignored(&ignores, path, is_directory)
626                {
627                    return None;
628                }
629                if is_directory {
630                    let mut directory = path.clone();
631                    directory.push(b'/');
632                    Some(directory)
633                } else {
634                    Some(path.clone())
635                }
636            })
637            .collect(),
638        StatusUntrackedMode::Normal => {
639            normal_untracked_paths_from_worktree(&worktree, &index, &ignores)
640        }
641        StatusUntrackedMode::None => Vec::new(),
642    };
643    for path in untracked_paths {
644        entries.push(ShortStatusEntry {
645            index: b'?',
646            worktree: b'?',
647            path,
648            head_mode: None,
649            index_mode: None,
650            worktree_mode: None,
651            head_oid: None,
652            index_oid: None,
653            submodule: None,
654        });
655    }
656    entries.sort_by(|left, right| {
657        status_sort_category(left)
658            .cmp(&status_sort_category(right))
659            .then_with(|| left.path.cmp(&right.path))
660    });
661    Ok(entries)
662}
663
664pub(crate) fn short_status_unmerged_entries(index: &Index) -> Vec<ShortStatusEntry> {
665    let mut by_path: BTreeMap<Vec<u8>, BTreeSet<u16>> = BTreeMap::new();
666    for entry in &index.entries {
667        let stage = entry.stage().as_u16();
668        if stage > 0 {
669            by_path
670                .entry(entry.path.as_bytes().to_vec())
671                .or_default()
672                .insert(stage);
673        }
674    }
675    by_path
676        .into_iter()
677        .map(|(path, stages)| {
678            let (index, worktree) = short_status_unmerged_codes(&stages);
679            ShortStatusEntry {
680                index,
681                worktree,
682                path,
683                head_mode: None,
684                index_mode: None,
685                worktree_mode: None,
686                head_oid: None,
687                index_oid: None,
688                submodule: None,
689            }
690        })
691        .collect()
692}
693
694pub(crate) fn short_status_unmerged_codes(stages: &BTreeSet<u16>) -> (u8, u8) {
695    match (
696        stages.contains(&1),
697        stages.contains(&2),
698        stages.contains(&3),
699    ) {
700        (true, false, false) => (b'D', b'D'),
701        (false, true, false) => (b'A', b'U'),
702        (true, true, false) => (b'U', b'D'),
703        (false, false, true) => (b'U', b'A'),
704        (true, false, true) => (b'D', b'U'),
705        (false, true, true) => (b'A', b'A'),
706        (true, true, true) => (b'U', b'U'),
707        (false, false, false) => (b'U', b'U'),
708    }
709}
710
711pub(crate) fn sparse_checkout_active_for_status(git_dir: &Path, index: &Index) -> bool {
712    index.is_sparse()
713        || index.entries.iter().any(IndexEntry::is_sparse_dir)
714        || sparse_checkout_config_enabled(git_dir)
715}
716
717pub(crate) fn sparse_checkout_active_for_borrowed_status(
718    git_dir: &Path,
719    index: &BorrowedIndex<'_>,
720) -> bool {
721    index
722        .entries
723        .iter()
724        .any(|entry| entry.mode == SPARSE_DIR_MODE && entry.is_skip_worktree())
725        || sparse_checkout_config_enabled(git_dir)
726}
727
728pub(crate) fn sparse_checkout_config_enabled(git_dir: &Path) -> bool {
729    GitConfig::read(git_dir.join("config"))
730        .ok()
731        .and_then(|config| config.get_bool("core", None, "sparseCheckout"))
732        == Some(true)
733        || GitConfig::read(git_dir.join("config.worktree"))
734            .ok()
735            .and_then(|config| config.get_bool("core", None, "sparseCheckout"))
736            == Some(true)
737}
738
739pub(crate) fn collect_status_entries_head_matches_index(
740    index: &BTreeMap<Vec<u8>, TrackedEntry>,
741    worktree: &BTreeMap<Vec<u8>, TrackedEntry>,
742    tracked_presence: &HashSet<Vec<u8>>,
743    stat_cache: &IndexStatCache,
744    sparse_checkout_active: bool,
745    submodule_dirt_map: &BTreeMap<Vec<u8>, u8>,
746    untracked_mode: StatusUntrackedMode,
747    entries: &mut Vec<ShortStatusEntry>,
748) {
749    for (path, index_entry) in index {
750        let intent_to_add = stat_cache
751            .index_entry(path)
752            .is_some_and(IndexEntry::is_intent_to_add);
753        let visible_index_entry = (!intent_to_add).then_some(index_entry);
754        let worktree_entry = worktree.get(path);
755        let worktree_present =
756            worktree_entry.is_some() || tracked_presence.contains(path.as_slice());
757        // Honor the skip-worktree bit literally unless sparse-checkout is active
758        // and the file is present (git clears the bit then -> changes reported).
759        let skip_worktree = stat_cache
760            .index_entry(path)
761            .is_some_and(index_entry_skip_worktree)
762            && (!sparse_checkout_active || !worktree_present);
763        let submodule = status_submodule_from_entries(
764            path,
765            index_entry,
766            worktree_entry,
767            submodule_dirt_map,
768            untracked_mode,
769        );
770        let worktree_code = match worktree_entry {
771            _ if skip_worktree => b' ',
772            None if intent_to_add => b' ',
773            None if !worktree_present => b'D',
774            Some(_) if intent_to_add => b'A',
775            Some(worktree_entry) if Some(worktree_entry) != visible_index_entry => {
776                status_change_code(index_entry.mode, worktree_entry.mode)
777            }
778            _ if submodule.is_some_and(|sub| sub.any()) => b'M',
779            _ => b' ',
780        };
781        if worktree_code != b' ' {
782            entries.push(ShortStatusEntry {
783                index: b' ',
784                worktree: worktree_code,
785                path: path.clone(),
786                head_mode: visible_index_entry.map(|entry| entry.mode),
787                index_mode: visible_index_entry.map(|entry| entry.mode),
788                worktree_mode: status_worktree_mode(
789                    visible_index_entry,
790                    worktree_entry,
791                    worktree_present,
792                ),
793                head_oid: visible_index_entry.map(|entry| entry.oid),
794                index_oid: visible_index_entry.map(|entry| entry.oid),
795                submodule: submodule.filter(|sub| sub.any()),
796            });
797        }
798    }
799}
800
801pub(crate) struct StatusComparisonInputs<'a> {
802    head: &'a BTreeMap<Vec<u8>, TrackedEntry>,
803    index: &'a BTreeMap<Vec<u8>, TrackedEntry>,
804    worktree: &'a BTreeMap<Vec<u8>, TrackedEntry>,
805    tracked_presence: &'a HashSet<Vec<u8>>,
806    stat_cache: &'a IndexStatCache,
807    sparse_checkout_active: bool,
808    submodule_dirt_map: &'a BTreeMap<Vec<u8>, u8>,
809    ignores: &'a IgnoreMatcher,
810}
811
812pub(crate) fn collect_status_entries_with_head(
813    inputs: StatusComparisonInputs<'_>,
814    untracked_mode: StatusUntrackedMode,
815    entries: &mut Vec<ShortStatusEntry>,
816) {
817    let mut paths = BTreeSet::new();
818    paths.extend(inputs.head.keys().cloned());
819    paths.extend(inputs.index.keys().cloned());
820    paths.extend(
821        inputs
822            .worktree
823            .keys()
824            .filter(|path| inputs.index.contains_key(*path))
825            .cloned(),
826    );
827
828    for path in paths {
829        let head_entry = inputs.head.get(&path);
830        let index_entry = inputs.index.get(&path);
831        let intent_to_add = inputs
832            .stat_cache
833            .index_entry(&path)
834            .is_some_and(IndexEntry::is_intent_to_add);
835        let visible_index_entry = index_entry.filter(|_| !intent_to_add);
836        let worktree_entry = inputs.worktree.get(&path);
837        let worktree_present =
838            worktree_entry.is_some() || inputs.tracked_presence.contains(path.as_slice());
839        if head_entry.is_none()
840            && index_entry.is_none()
841            && worktree_entry.is_some()
842            && inputs.ignores.is_ignored(&path, false)
843        {
844            continue;
845        }
846        let submodule = match visible_index_entry {
847            Some(index_entry) => status_submodule_from_entries(
848                &path,
849                index_entry,
850                worktree_entry,
851                inputs.submodule_dirt_map,
852                untracked_mode,
853            ),
854            None => None,
855        };
856        // Honor the skip-worktree bit literally unless sparse-checkout is active
857        // and the file is present (git clears the bit then -> changes reported).
858        let skip_worktree = visible_index_entry.is_some_and(|_| {
859            inputs
860                .stat_cache
861                .index_entry(&path)
862                .is_some_and(index_entry_skip_worktree)
863        }) && (!inputs.sparse_checkout_active || !worktree_present);
864        let (index_code, worktree_code) =
865            if head_entry.is_none() && index_entry.is_none() && worktree_entry.is_some() {
866                (b'?', b'?')
867            } else {
868                let index_code = match (head_entry, visible_index_entry) {
869                    (None, Some(_)) => b'A',
870                    (Some(_), None) => b'D',
871                    (Some(left), Some(right)) if left != right => {
872                        status_change_code(left.mode, right.mode)
873                    }
874                    _ => b' ',
875                };
876                let worktree_code = match (visible_index_entry, worktree_entry) {
877                    (None, Some(_)) if intent_to_add => b'A',
878                    (None, Some(_)) => b'?',
879                    (None, None) if intent_to_add => b' ',
880                    (Some(_), _) if skip_worktree => b' ',
881                    (Some(_), None) if !worktree_present => b'D',
882                    (Some(left), Some(right)) if left != right => {
883                        status_change_code(left.mode, right.mode)
884                    }
885                    _ if submodule.is_some_and(|sub| sub.any()) => b'M',
886                    _ => b' ',
887                };
888                (index_code, worktree_code)
889            };
890        if index_code != b' ' || worktree_code != b' ' {
891            let worktree_mode = if skip_worktree {
892                visible_index_entry.map(|entry| entry.mode)
893            } else {
894                status_worktree_mode(visible_index_entry, worktree_entry, worktree_present)
895            };
896            entries.push(ShortStatusEntry {
897                index: index_code,
898                worktree: worktree_code,
899                path,
900                head_mode: head_entry.map(|entry| entry.mode),
901                index_mode: visible_index_entry.map(|entry| entry.mode),
902                worktree_mode,
903                head_oid: head_entry.map(|entry| entry.oid),
904                index_oid: visible_index_entry.map(|entry| entry.oid),
905                submodule: submodule.filter(|sub| sub.any()),
906            });
907        }
908    }
909}
910
911pub(crate) fn status_worktree_mode(
912    index_entry: Option<&TrackedEntry>,
913    worktree_entry: Option<&TrackedEntry>,
914    worktree_present: bool,
915) -> Option<u32> {
916    worktree_entry.map(|entry| entry.mode).or_else(|| {
917        worktree_present
918            .then(|| index_entry.map(|entry| entry.mode))
919            .flatten()
920    })
921}
922
923pub(crate) fn status_submodule_from_entries(
924    path: &[u8],
925    index_entry: &TrackedEntry,
926    worktree_entry: Option<&TrackedEntry>,
927    submodule_dirt_map: &BTreeMap<Vec<u8>, u8>,
928    _untracked_mode: StatusUntrackedMode,
929) -> Option<SubmoduleStatus> {
930    let worktree_entry = worktree_entry?;
931    if !sley_index::is_gitlink(index_entry.mode) || !sley_index::is_gitlink(worktree_entry.mode) {
932        return None;
933    }
934    let dirt = submodule_dirt_map.get(path).copied().unwrap_or(0);
935    Some(SubmoduleStatus {
936        new_commits: index_entry.oid != worktree_entry.oid,
937        modified_content: dirt & DIRTY_SUBMODULE_MODIFIED != 0,
938        untracked_content: dirt & DIRTY_SUBMODULE_UNTRACKED != 0,
939    })
940}
941
942pub(crate) fn short_status_tracked_only(
943    worktree_root: &Path,
944    git_dir: &Path,
945    format: ObjectFormat,
946    db: &FileObjectDatabase,
947    index: &Index,
948    stat_cache: &IndexStatCache,
949    head_matches_index: bool,
950    sparse_checkout_active: bool,
951    untracked_mode: StatusUntrackedMode,
952) -> Result<Vec<ShortStatusEntry>> {
953    let normal_entry_count = index
954        .entries
955        .iter()
956        .filter(|entry| entry.stage() == Stage::Normal)
957        .count();
958    if head_matches_index && normal_entry_count >= 512 {
959        return short_status_tracked_only_head_matches_index_parallel(
960            worktree_root,
961            git_dir,
962            format,
963            index,
964            stat_cache,
965            sparse_checkout_active,
966            untracked_mode,
967        );
968    }
969    let head = if head_matches_index {
970        None
971    } else {
972        Some(head_tree_entries(git_dir, format, db)?)
973    };
974    if !head_matches_index && normal_entry_count >= 512 {
975        if let Some(head) = head.as_ref() {
976            return short_status_tracked_only_with_head_parallel(
977                worktree_root,
978                git_dir,
979                format,
980                index,
981                stat_cache,
982                head,
983                sparse_checkout_active,
984                untracked_mode,
985            );
986        }
987    }
988    let mut clean_filter = None;
989    let mut entries = Vec::new();
990    for entry in index
991        .entries
992        .iter()
993        .filter(|entry| entry.stage() == Stage::Normal)
994    {
995        let path = entry.path.as_bytes();
996        let index_entry = TrackedEntry {
997            mode: entry.mode,
998            oid: entry.oid,
999        };
1000        let head_entry = if head_matches_index {
1001            (!entry.is_intent_to_add()).then_some(&index_entry)
1002        } else {
1003            head.as_ref().and_then(|head| head.get(path))
1004        };
1005        let worktree_entry = worktree_entry_for_index_entry_with_attributes(
1006            worktree_root,
1007            git_dir,
1008            format,
1009            entry,
1010            stat_cache,
1011            &mut clean_filter,
1012        )?;
1013        let submodule = tracked_only_submodule_status(
1014            worktree_root,
1015            path,
1016            &index_entry,
1017            worktree_entry.as_ref(),
1018            untracked_mode,
1019        )?;
1020        let visible_index_entry = (!entry.is_intent_to_add()).then_some(&index_entry);
1021        let index_code = match (head_entry, visible_index_entry) {
1022            (None, Some(_)) => b'A',
1023            (Some(_), None) => b'D',
1024            (Some(head_entry), Some(index_entry)) if *head_entry != *index_entry => {
1025                status_change_code(head_entry.mode, index_entry.mode)
1026            }
1027            _ => b' ',
1028        };
1029        // Honor the skip-worktree bit literally unless sparse-checkout is active
1030        // and the file is present (git clears the bit then -> changes reported).
1031        let skip_worktree =
1032            entry.is_skip_worktree() && (!sparse_checkout_active || worktree_entry.is_none());
1033        let worktree_code = match worktree_entry.as_ref() {
1034            _ if skip_worktree => b' ',
1035            None if entry.is_intent_to_add() => b' ',
1036            None => b'D',
1037            Some(_) if entry.is_intent_to_add() => b'A',
1038            Some(worktree_entry) if Some(worktree_entry) != visible_index_entry => {
1039                status_change_code(index_entry.mode, worktree_entry.mode)
1040            }
1041            _ if submodule.is_some_and(|sub| sub.any()) => b'M',
1042            _ => b' ',
1043        };
1044        if index_code != b' ' || worktree_code != b' ' {
1045            entries.push(ShortStatusEntry {
1046                index: index_code,
1047                worktree: worktree_code,
1048                path: path.to_vec(),
1049                head_mode: head_entry.map(|entry| entry.mode),
1050                index_mode: visible_index_entry.map(|entry| entry.mode),
1051                worktree_mode: if skip_worktree {
1052                    visible_index_entry.map(|entry| entry.mode)
1053                } else {
1054                    worktree_entry.as_ref().map(|entry| entry.mode)
1055                },
1056                head_oid: head_entry.map(|entry| entry.oid),
1057                index_oid: visible_index_entry.map(|entry| entry.oid),
1058                submodule: submodule.filter(|sub| sub.any()),
1059            });
1060        }
1061    }
1062    if let Some(head) = head.as_ref() {
1063        let index_paths = index
1064            .entries
1065            .iter()
1066            .filter(|entry| entry.stage() == Stage::Normal)
1067            .map(|entry| entry.path.as_bytes().to_vec())
1068            .collect::<HashSet<_>>();
1069        for (path, head_entry) in head {
1070            if index_paths.contains(path.as_slice()) {
1071                continue;
1072            }
1073            entries.push(ShortStatusEntry {
1074                index: b'D',
1075                worktree: b' ',
1076                path: path.clone(),
1077                head_mode: Some(head_entry.mode),
1078                index_mode: None,
1079                worktree_mode: None,
1080                head_oid: Some(head_entry.oid),
1081                index_oid: None,
1082                submodule: None,
1083            });
1084        }
1085    }
1086    entries.sort_by(|left, right| {
1087        status_sort_category(left)
1088            .cmp(&status_sort_category(right))
1089            .then_with(|| left.path.cmp(&right.path))
1090    });
1091    Ok(entries)
1092}
1093
1094pub(crate) fn short_status_borrowed_head_matches_index_if_possible(
1095    worktree_root: &Path,
1096    git_dir: &Path,
1097    format: ObjectFormat,
1098    db: &FileObjectDatabase,
1099    untracked_mode: StatusUntrackedMode,
1100) -> Result<Option<Vec<ShortStatusEntry>>> {
1101    let index_path = repository_index_path(git_dir);
1102    let index_metadata = match fs::metadata(&index_path) {
1103        Ok(metadata) => metadata,
1104        Err(err)
1105            if err.kind() == std::io::ErrorKind::NotFound
1106                && matches!(untracked_mode, StatusUntrackedMode::None) =>
1107        {
1108            return Ok(Some(Vec::new()));
1109        }
1110        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
1111        Err(err) => return Err(err.into()),
1112    };
1113    let index_bytes = read_borrowed_index_bytes(&index_path)?;
1114    status_profile_mem(
1115        "after_index_bytes",
1116        &[
1117            ("index_file_bytes", index_metadata.len() as usize),
1118            ("index_bytes_len", index_bytes.as_ref().len()),
1119            (
1120                "index_bytes_mapped",
1121                usize::from(matches!(index_bytes, BorrowedIndexBytes::Mapped(_))),
1122            ),
1123        ],
1124    );
1125    let borrowed = match BorrowedIndex::parse(index_bytes.as_ref(), format) {
1126        Ok(index) => index,
1127        Err(GitError::Unsupported(_)) => return Ok(None),
1128        Err(err) => return Err(err),
1129    };
1130    status_profile_mem(
1131        "after_borrowed_parse",
1132        &[
1133            ("index_file_bytes", index_metadata.len() as usize),
1134            ("index_bytes_len", index_bytes.as_ref().len()),
1135            (
1136                "index_bytes_mapped",
1137                usize::from(matches!(index_bytes, BorrowedIndexBytes::Mapped(_))),
1138            ),
1139            ("borrowed_entries_len", borrowed.entries.len()),
1140            ("borrowed_entries_cap", borrowed.entries.capacity()),
1141            (
1142                "borrowed_entry_size",
1143                std::mem::size_of::<IndexEntryRef<'_>>(),
1144            ),
1145            (
1146                "borrowed_entries_cap_bytes",
1147                borrowed.entries.capacity() * std::mem::size_of::<IndexEntryRef<'_>>(),
1148            ),
1149            ("borrowed_extensions_len", borrowed.extensions.len()),
1150        ],
1151    );
1152    let sparse_checkout_active = sparse_checkout_active_for_borrowed_status(git_dir, &borrowed);
1153    if borrowed
1154        .entries
1155        .iter()
1156        .any(|entry| entry.mode == SPARSE_DIR_MODE && entry.is_skip_worktree())
1157    {
1158        return Ok(None);
1159    }
1160    if borrowed
1161        .entries
1162        .iter()
1163        .any(|entry| entry.stage() != Stage::Normal)
1164    {
1165        return Ok(None);
1166    }
1167    status_profile_mem(
1168        "after_sparse_scan",
1169        &[
1170            ("borrowed_entries_len", borrowed.entries.len()),
1171            (
1172                "borrowed_entries_cap_bytes",
1173                borrowed.entries.capacity() * std::mem::size_of::<IndexEntryRef<'_>>(),
1174            ),
1175            (
1176                "sparse_checkout_active",
1177                usize::from(sparse_checkout_active),
1178            ),
1179        ],
1180    );
1181    let Some(head_tree_oid) = resolve_head_tree_oid(git_dir, format, db)? else {
1182        return Ok(None);
1183    };
1184    status_profile_mem(
1185        "after_head_tree_oid",
1186        &[
1187            ("borrowed_entries_len", borrowed.entries.len()),
1188            (
1189                "borrowed_entries_cap_bytes",
1190                borrowed.entries.capacity() * std::mem::size_of::<IndexEntryRef<'_>>(),
1191            ),
1192        ],
1193    );
1194    let stage0_entry_count = borrowed
1195        .entries
1196        .iter()
1197        .filter(|entry| entry.stage() == Stage::Normal)
1198        .count();
1199    status_profile_mem(
1200        "after_stage0_count",
1201        &[
1202            ("stage0_entry_count", stage0_entry_count),
1203            ("borrowed_entries_len", borrowed.entries.len()),
1204        ],
1205    );
1206    if !head_matches_borrowed_index_from_cache_tree(
1207        &borrowed,
1208        format,
1209        &head_tree_oid,
1210        stage0_entry_count,
1211    )? {
1212        return Ok(None);
1213    }
1214    status_profile_mem(
1215        "after_head_matches_index",
1216        &[
1217            ("stage0_entry_count", stage0_entry_count),
1218            ("borrowed_entries_len", borrowed.entries.len()),
1219            (
1220                "borrowed_entries_cap_bytes",
1221                borrowed.entries.capacity() * std::mem::size_of::<IndexEntryRef<'_>>(),
1222            ),
1223        ],
1224    );
1225
1226    let index_mtime = file_mtime_parts(&index_metadata);
1227    let stat_cache = IndexStatCache::from_index_mtime_only(index_mtime);
1228    let profile_enabled = StatusProfileCounters::enabled();
1229    let mut profile = profile_enabled.then(|| StatusProfileCounters {
1230        fast_path_borrowed: true,
1231        ..StatusProfileCounters::default()
1232    });
1233
1234    if matches!(untracked_mode, StatusUntrackedMode::None) {
1235        let tracked_start = Instant::now();
1236        let entries = short_status_borrowed_tracked_only_head_matches_index_parallel(
1237            worktree_root,
1238            git_dir,
1239            format,
1240            &borrowed,
1241            &stat_cache,
1242            sparse_checkout_active,
1243            untracked_mode,
1244        )?;
1245        if let Some(profile) = profile.as_mut() {
1246            profile.tracked_elapsed_us = tracked_start.elapsed().as_micros();
1247            profile.emit();
1248        }
1249        return Ok(Some(entries));
1250    }
1251
1252    if stage0_entry_count < STATUS_BORROWED_OVERLAP_MIN_STAGE0 {
1253        let tracked_start = Instant::now();
1254        let mut entries = short_status_borrowed_tracked_only_head_matches_index_parallel(
1255            worktree_root,
1256            git_dir,
1257            format,
1258            &borrowed,
1259            &stat_cache,
1260            sparse_checkout_active,
1261            untracked_mode,
1262        )?;
1263        if let Some(profile) = profile.as_mut() {
1264            profile.tracked_elapsed_us = tracked_start.elapsed().as_micros();
1265        }
1266        let mut ignores = IgnoreMatcher::from_worktree_base(worktree_root)?;
1267        let untracked_start = Instant::now();
1268        let untracked_paths = status_untracked_paths_from_borrowed_index(
1269            worktree_root,
1270            git_dir,
1271            &borrowed,
1272            &mut ignores,
1273            untracked_mode,
1274            profile.as_mut(),
1275        )?;
1276        if let Some(profile) = profile.as_mut() {
1277            profile.untracked_elapsed_us = untracked_start.elapsed().as_micros();
1278            profile.untracked_rows = untracked_paths.len() as u64;
1279        }
1280        let render_start = Instant::now();
1281        append_untracked_status_entries(&mut entries, untracked_paths);
1282        if let Some(profile) = profile.as_mut() {
1283            profile.render_elapsed_us = render_start.elapsed().as_micros();
1284            profile.emit();
1285        }
1286        return Ok(Some(entries));
1287    }
1288
1289    if let Some(profile) = profile.as_mut() {
1290        profile.overlap_enabled = true;
1291    }
1292    let executor = StatusExecutor::new();
1293    if profile_enabled {
1294        let (mut entries, untracked_paths, untracked_profile) =
1295            std::thread::scope(|scope| -> Result<_> {
1296                let tracked = executor.spawn(scope, "status-tracked", || {
1297                    let start = Instant::now();
1298                    short_status_borrowed_tracked_only_head_matches_index_parallel(
1299                        worktree_root,
1300                        git_dir,
1301                        format,
1302                        &borrowed,
1303                        &stat_cache,
1304                        sparse_checkout_active,
1305                        untracked_mode,
1306                    )
1307                    .map(|entries| (entries, start.elapsed().as_micros()))
1308                })?;
1309                let untracked = executor.spawn(
1310                    scope,
1311                    "status-untracked",
1312                    || -> Result<(Vec<Vec<u8>>, StatusProfileCounters)> {
1313                        let mut local_profile = StatusProfileCounters::default();
1314                        let mut ignores = IgnoreMatcher::from_worktree_base(worktree_root)?;
1315                        let start = Instant::now();
1316                        let paths = status_untracked_paths_from_borrowed_index(
1317                            worktree_root,
1318                            git_dir,
1319                            &borrowed,
1320                            &mut ignores,
1321                            untracked_mode,
1322                            Some(&mut local_profile),
1323                        )?;
1324                        local_profile.untracked_elapsed_us = start.elapsed().as_micros();
1325                        local_profile.untracked_rows = paths.len() as u64;
1326                        Ok((paths, local_profile))
1327                    },
1328                )?;
1329                let (entries, tracked_elapsed_us) = tracked.join()?;
1330                let (untracked_paths, untracked_profile) = untracked.join()?;
1331                if let Some(profile) = profile.as_mut() {
1332                    profile.tracked_elapsed_us = tracked_elapsed_us;
1333                }
1334                Ok((entries, untracked_paths, Some(untracked_profile)))
1335            })?;
1336        if let Some(profile) = profile.as_mut() {
1337            if let Some(untracked_profile) = untracked_profile {
1338                profile.merge_untracked(untracked_profile);
1339            }
1340        }
1341        let render_start = Instant::now();
1342        append_untracked_status_entries(&mut entries, untracked_paths);
1343        if let Some(profile) = profile.as_mut() {
1344            profile.render_elapsed_us = render_start.elapsed().as_micros();
1345            profile.emit();
1346        }
1347        return Ok(Some(entries));
1348    }
1349    let (mut entries, untracked_paths) = std::thread::scope(|scope| -> Result<_> {
1350        let tracked = executor.spawn(scope, "status-tracked", || {
1351            short_status_borrowed_tracked_only_head_matches_index_parallel(
1352                worktree_root,
1353                git_dir,
1354                format,
1355                &borrowed,
1356                &stat_cache,
1357                sparse_checkout_active,
1358                untracked_mode,
1359            )
1360        })?;
1361        let untracked = executor.spawn(scope, "status-untracked", || -> Result<Vec<Vec<u8>>> {
1362            let mut ignores = IgnoreMatcher::from_worktree_base(worktree_root)?;
1363            status_untracked_paths_from_borrowed_index(
1364                worktree_root,
1365                git_dir,
1366                &borrowed,
1367                &mut ignores,
1368                untracked_mode,
1369                None,
1370            )
1371        })?;
1372        let entries = tracked.join()?;
1373        let untracked_paths = untracked.join()?;
1374        Ok((entries, untracked_paths))
1375    })?;
1376    let render_start = Instant::now();
1377    append_untracked_status_entries(&mut entries, untracked_paths);
1378    if let Some(profile) = profile.as_mut() {
1379        profile.render_elapsed_us = render_start.elapsed().as_micros();
1380        profile.emit();
1381    }
1382    Ok(Some(entries))
1383}
1384
1385pub(crate) fn stream_short_status_borrowed_head_matches_index_if_possible<F>(
1386    worktree_root: &Path,
1387    git_dir: &Path,
1388    format: ObjectFormat,
1389    db: &FileObjectDatabase,
1390    untracked_mode: StatusUntrackedMode,
1391    emit: &mut F,
1392) -> Result<Option<()>>
1393where
1394    F: for<'a> FnMut(ShortStatusRow<'a>) -> Result<StreamControl>,
1395{
1396    let index_path = repository_index_path(git_dir);
1397    let index_metadata = match fs::metadata(&index_path) {
1398        Ok(metadata) => metadata,
1399        Err(err)
1400            if err.kind() == std::io::ErrorKind::NotFound
1401                && matches!(untracked_mode, StatusUntrackedMode::None) =>
1402        {
1403            return Ok(Some(()));
1404        }
1405        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
1406        Err(err) => return Err(err.into()),
1407    };
1408    let index_bytes = read_borrowed_index_bytes(&index_path)?;
1409    status_profile_mem(
1410        "after_index_bytes",
1411        &[
1412            ("index_file_bytes", index_metadata.len() as usize),
1413            ("index_bytes_len", index_bytes.as_ref().len()),
1414            (
1415                "index_bytes_mapped",
1416                usize::from(matches!(index_bytes, BorrowedIndexBytes::Mapped(_))),
1417            ),
1418        ],
1419    );
1420    let borrowed = match BorrowedIndex::parse(index_bytes.as_ref(), format) {
1421        Ok(index) => index,
1422        Err(GitError::Unsupported(_)) => return Ok(None),
1423        Err(err) => return Err(err),
1424    };
1425    status_profile_mem(
1426        "after_borrowed_parse",
1427        &[
1428            ("index_file_bytes", index_metadata.len() as usize),
1429            ("index_bytes_len", index_bytes.as_ref().len()),
1430            (
1431                "index_bytes_mapped",
1432                usize::from(matches!(index_bytes, BorrowedIndexBytes::Mapped(_))),
1433            ),
1434            ("borrowed_entries_len", borrowed.entries.len()),
1435            ("borrowed_entries_cap", borrowed.entries.capacity()),
1436            (
1437                "borrowed_entry_size",
1438                std::mem::size_of::<IndexEntryRef<'_>>(),
1439            ),
1440            (
1441                "borrowed_entries_cap_bytes",
1442                borrowed.entries.capacity() * std::mem::size_of::<IndexEntryRef<'_>>(),
1443            ),
1444            ("borrowed_extensions_len", borrowed.extensions.len()),
1445        ],
1446    );
1447    let sparse_checkout_active = sparse_checkout_active_for_borrowed_status(git_dir, &borrowed);
1448    if borrowed
1449        .entries
1450        .iter()
1451        .any(|entry| entry.mode == SPARSE_DIR_MODE && entry.is_skip_worktree())
1452    {
1453        return Ok(None);
1454    }
1455    if borrowed
1456        .entries
1457        .iter()
1458        .any(|entry| entry.stage() != Stage::Normal)
1459    {
1460        return Ok(None);
1461    }
1462    status_profile_mem(
1463        "after_sparse_scan",
1464        &[
1465            ("borrowed_entries_len", borrowed.entries.len()),
1466            (
1467                "borrowed_entries_cap_bytes",
1468                borrowed.entries.capacity() * std::mem::size_of::<IndexEntryRef<'_>>(),
1469            ),
1470            (
1471                "sparse_checkout_active",
1472                usize::from(sparse_checkout_active),
1473            ),
1474        ],
1475    );
1476    let Some(head_tree_oid) = resolve_head_tree_oid(git_dir, format, db)? else {
1477        return Ok(None);
1478    };
1479    status_profile_mem(
1480        "after_head_tree_oid",
1481        &[
1482            ("borrowed_entries_len", borrowed.entries.len()),
1483            (
1484                "borrowed_entries_cap_bytes",
1485                borrowed.entries.capacity() * std::mem::size_of::<IndexEntryRef<'_>>(),
1486            ),
1487        ],
1488    );
1489    let stage0_entry_count = borrowed
1490        .entries
1491        .iter()
1492        .filter(|entry| entry.stage() == Stage::Normal)
1493        .count();
1494    status_profile_mem(
1495        "after_stage0_count",
1496        &[
1497            ("stage0_entry_count", stage0_entry_count),
1498            ("borrowed_entries_len", borrowed.entries.len()),
1499        ],
1500    );
1501    if !head_matches_borrowed_index_from_cache_tree(
1502        &borrowed,
1503        format,
1504        &head_tree_oid,
1505        stage0_entry_count,
1506    )? {
1507        return Ok(None);
1508    }
1509    status_profile_mem(
1510        "after_head_matches_index",
1511        &[
1512            ("stage0_entry_count", stage0_entry_count),
1513            ("borrowed_entries_len", borrowed.entries.len()),
1514            (
1515                "borrowed_entries_cap_bytes",
1516                borrowed.entries.capacity() * std::mem::size_of::<IndexEntryRef<'_>>(),
1517            ),
1518        ],
1519    );
1520
1521    let index_mtime = file_mtime_parts(&index_metadata);
1522    let stat_cache = IndexStatCache::from_index_mtime_only(index_mtime);
1523    let profile_enabled = StatusProfileCounters::enabled();
1524    let mut profile = profile_enabled.then(|| StatusProfileCounters {
1525        fast_path_borrowed: true,
1526        ..StatusProfileCounters::default()
1527    });
1528
1529    if matches!(untracked_mode, StatusUntrackedMode::None) {
1530        let tracked_start = Instant::now();
1531        let tracked_control =
1532            stream_short_status_borrowed_tracked_only_head_matches_index_parallel(
1533                worktree_root,
1534                git_dir,
1535                format,
1536                &borrowed,
1537                &stat_cache,
1538                sparse_checkout_active,
1539                untracked_mode,
1540                emit,
1541            )?;
1542        if let Some(profile) = profile.as_mut() {
1543            profile.tracked_elapsed_us = tracked_start.elapsed().as_micros();
1544        }
1545        if let Some(profile) = profile.as_ref() {
1546            profile.emit();
1547        }
1548        if tracked_control.is_stop() {
1549            return Ok(Some(()));
1550        }
1551        return Ok(Some(()));
1552    }
1553
1554    if stage0_entry_count < STATUS_BORROWED_OVERLAP_MIN_STAGE0 {
1555        let tracked_start = Instant::now();
1556        let tracked_control =
1557            stream_short_status_borrowed_tracked_only_head_matches_index_parallel(
1558                worktree_root,
1559                git_dir,
1560                format,
1561                &borrowed,
1562                &stat_cache,
1563                sparse_checkout_active,
1564                untracked_mode,
1565                emit,
1566            )?;
1567        if let Some(profile) = profile.as_mut() {
1568            profile.tracked_elapsed_us = tracked_start.elapsed().as_micros();
1569        }
1570        if tracked_control.is_stop() {
1571            if let Some(profile) = profile.as_ref() {
1572                profile.emit();
1573            }
1574            return Ok(Some(()));
1575        }
1576        let mut ignores = IgnoreMatcher::from_worktree_base(worktree_root)?;
1577        let untracked_start = Instant::now();
1578        stream_status_untracked_paths_from_borrowed_index(
1579            worktree_root,
1580            git_dir,
1581            &borrowed,
1582            &mut ignores,
1583            untracked_mode,
1584            profile.as_mut(),
1585            emit_untracked_status_entry(emit),
1586        )?;
1587        if let Some(profile) = profile.as_mut() {
1588            profile.untracked_elapsed_us = untracked_start.elapsed().as_micros();
1589            profile.emit();
1590        }
1591        return Ok(Some(()));
1592    }
1593
1594    if let Some(profile) = profile.as_mut() {
1595        profile.overlap_enabled = true;
1596    }
1597    let executor = StatusExecutor::new();
1598    let (tracked_control, untracked_paths, untracked_profile) =
1599        std::thread::scope(|scope| -> Result<_> {
1600            let untracked = executor.spawn(
1601                scope,
1602                "status-untracked",
1603                || -> Result<(Vec<Vec<u8>>, StatusProfileCounters)> {
1604                    let mut local_profile = StatusProfileCounters::default();
1605                    let mut ignores = IgnoreMatcher::from_worktree_base(worktree_root)?;
1606                    ignores.emit_memory_profile("after_untracked_ignore");
1607                    let start = Instant::now();
1608                    let paths = status_untracked_paths_from_borrowed_index(
1609                        worktree_root,
1610                        git_dir,
1611                        &borrowed,
1612                        &mut ignores,
1613                        untracked_mode,
1614                        profile_enabled.then_some(&mut local_profile),
1615                    )?;
1616                    status_profile_mem(
1617                        "after_untracked_collect",
1618                        &[
1619                            ("untracked_paths_len", paths.len()),
1620                            ("untracked_paths_cap", paths.capacity()),
1621                            (
1622                                "untracked_paths_cap_bytes",
1623                                paths.capacity() * std::mem::size_of::<Vec<u8>>(),
1624                            ),
1625                            (
1626                                "untracked_path_payload_bytes",
1627                                paths.iter().map(Vec::capacity).sum(),
1628                            ),
1629                        ],
1630                    );
1631                    local_profile.untracked_elapsed_us = start.elapsed().as_micros();
1632                    local_profile.untracked_rows = paths.len() as u64;
1633                    Ok((paths, local_profile))
1634                },
1635            )?;
1636            let tracked_start = Instant::now();
1637            let tracked_control =
1638                stream_short_status_borrowed_tracked_only_head_matches_index_parallel(
1639                    worktree_root,
1640                    git_dir,
1641                    format,
1642                    &borrowed,
1643                    &stat_cache,
1644                    sparse_checkout_active,
1645                    untracked_mode,
1646                    emit,
1647                )?;
1648            let tracked_elapsed_us = tracked_start.elapsed().as_micros();
1649            let (untracked_paths, untracked_profile) = untracked.join()?;
1650            if let Some(profile) = profile.as_mut() {
1651                profile.tracked_elapsed_us = tracked_elapsed_us;
1652            }
1653            Ok((
1654                tracked_control,
1655                untracked_paths,
1656                profile_enabled.then_some(untracked_profile),
1657            ))
1658        })?;
1659    status_profile_mem(
1660        "after_join",
1661        &[
1662            ("untracked_paths_len", untracked_paths.len()),
1663            ("untracked_paths_cap", untracked_paths.capacity()),
1664            (
1665                "untracked_paths_cap_bytes",
1666                untracked_paths.capacity() * std::mem::size_of::<Vec<u8>>(),
1667            ),
1668            (
1669                "untracked_path_payload_bytes",
1670                untracked_paths.iter().map(Vec::capacity).sum(),
1671            ),
1672        ],
1673    );
1674    if tracked_control.is_stop() {
1675        if let Some(profile) = profile.as_mut()
1676            && let Some(untracked_profile) = untracked_profile
1677        {
1678            profile.merge_untracked(untracked_profile);
1679            profile.emit();
1680        }
1681        return Ok(Some(()));
1682    }
1683    if let Some(profile) = profile.as_mut()
1684        && let Some(untracked_profile) = untracked_profile
1685    {
1686        profile.merge_untracked(untracked_profile);
1687    }
1688    let render_start = Instant::now();
1689    for path in untracked_paths {
1690        let row = untracked_status_row(&path);
1691        if emit(row)?.is_stop() {
1692            break;
1693        }
1694    }
1695    if let Some(profile) = profile.as_mut() {
1696        profile.render_elapsed_us = render_start.elapsed().as_micros();
1697        profile.emit();
1698    }
1699    status_profile_mem("after_render", &[]);
1700    Ok(Some(()))
1701}
1702
1703pub(crate) fn short_status_borrowed_head_matches_index_count_if_possible(
1704    worktree_root: &Path,
1705    git_dir: &Path,
1706    format: ObjectFormat,
1707    db: &FileObjectDatabase,
1708    untracked_mode: StatusUntrackedMode,
1709) -> Result<Option<usize>> {
1710    let index_path = repository_index_path(git_dir);
1711    let index_metadata = match fs::metadata(&index_path) {
1712        Ok(metadata) => metadata,
1713        Err(err)
1714            if err.kind() == std::io::ErrorKind::NotFound
1715                && matches!(untracked_mode, StatusUntrackedMode::None) =>
1716        {
1717            return Ok(Some(0));
1718        }
1719        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
1720        Err(err) => return Err(err.into()),
1721    };
1722    let index_bytes = read_borrowed_index_bytes(&index_path)?;
1723    let borrowed = match BorrowedIndex::parse(index_bytes.as_ref(), format) {
1724        Ok(index) => index,
1725        Err(GitError::Unsupported(_)) => return Ok(None),
1726        Err(err) => return Err(err),
1727    };
1728    let sparse_checkout_active = sparse_checkout_active_for_borrowed_status(git_dir, &borrowed);
1729    if borrowed
1730        .entries
1731        .iter()
1732        .any(|entry| entry.mode == SPARSE_DIR_MODE && entry.is_skip_worktree())
1733    {
1734        return Ok(None);
1735    }
1736    let Some(head_tree_oid) = resolve_head_tree_oid(git_dir, format, db)? else {
1737        return Ok(None);
1738    };
1739    let stage0_entry_count = borrowed
1740        .entries
1741        .iter()
1742        .filter(|entry| entry.stage() == Stage::Normal)
1743        .count();
1744    if !head_matches_borrowed_index_from_cache_tree(
1745        &borrowed,
1746        format,
1747        &head_tree_oid,
1748        stage0_entry_count,
1749    )? {
1750        return Ok(None);
1751    }
1752
1753    let index_mtime = file_mtime_parts(&index_metadata);
1754    let stat_cache = IndexStatCache::from_index_mtime_only(index_mtime);
1755    let profile_enabled = StatusProfileCounters::enabled();
1756    let mut profile = profile_enabled.then(|| StatusProfileCounters {
1757        fast_path_borrowed: true,
1758        ..StatusProfileCounters::default()
1759    });
1760
1761    if matches!(untracked_mode, StatusUntrackedMode::None) {
1762        let tracked_start = Instant::now();
1763        let count = short_status_borrowed_tracked_only_head_matches_index_count_parallel(
1764            worktree_root,
1765            git_dir,
1766            format,
1767            &borrowed,
1768            &stat_cache,
1769            sparse_checkout_active,
1770            untracked_mode,
1771        )?;
1772        if let Some(profile) = profile.as_mut() {
1773            profile.tracked_elapsed_us = tracked_start.elapsed().as_micros();
1774            profile.emit();
1775        }
1776        return Ok(Some(count));
1777    }
1778
1779    if stage0_entry_count < STATUS_BORROWED_OVERLAP_MIN_STAGE0 {
1780        let tracked_start = Instant::now();
1781        let tracked_count = short_status_borrowed_tracked_only_head_matches_index_count_parallel(
1782            worktree_root,
1783            git_dir,
1784            format,
1785            &borrowed,
1786            &stat_cache,
1787            sparse_checkout_active,
1788            untracked_mode,
1789        )?;
1790        if let Some(profile) = profile.as_mut() {
1791            profile.tracked_elapsed_us = tracked_start.elapsed().as_micros();
1792        }
1793        let mut ignores = IgnoreMatcher::from_worktree_base(worktree_root)?;
1794        let untracked_start = Instant::now();
1795        let untracked_count = status_untracked_count_from_borrowed_index(
1796            worktree_root,
1797            git_dir,
1798            &borrowed,
1799            &mut ignores,
1800            untracked_mode,
1801            profile.as_mut(),
1802        )?;
1803        if let Some(profile) = profile.as_mut() {
1804            profile.untracked_elapsed_us = untracked_start.elapsed().as_micros();
1805            profile.untracked_rows = untracked_count as u64;
1806            profile.emit();
1807        }
1808        return Ok(Some(tracked_count + untracked_count));
1809    }
1810
1811    if let Some(profile) = profile.as_mut() {
1812        profile.overlap_enabled = true;
1813    }
1814    let executor = StatusExecutor::new();
1815    let (tracked_count, untracked_count, untracked_profile) =
1816        std::thread::scope(|scope| -> Result<_> {
1817            let tracked = executor.spawn(scope, "status-tracked", || {
1818                let start = Instant::now();
1819                short_status_borrowed_tracked_only_head_matches_index_count_parallel(
1820                    worktree_root,
1821                    git_dir,
1822                    format,
1823                    &borrowed,
1824                    &stat_cache,
1825                    sparse_checkout_active,
1826                    untracked_mode,
1827                )
1828                .map(|count| (count, start.elapsed().as_micros()))
1829            })?;
1830            let untracked = executor.spawn(
1831                scope,
1832                "status-untracked",
1833                || -> Result<(usize, StatusProfileCounters)> {
1834                    let mut local_profile = StatusProfileCounters::default();
1835                    let mut ignores = IgnoreMatcher::from_worktree_base(worktree_root)?;
1836                    let start = Instant::now();
1837                    let count = status_untracked_count_from_borrowed_index(
1838                        worktree_root,
1839                        git_dir,
1840                        &borrowed,
1841                        &mut ignores,
1842                        untracked_mode,
1843                        profile_enabled.then_some(&mut local_profile),
1844                    )?;
1845                    local_profile.untracked_elapsed_us = start.elapsed().as_micros();
1846                    local_profile.untracked_rows = count as u64;
1847                    Ok((count, local_profile))
1848                },
1849            )?;
1850            let (tracked_count, tracked_elapsed_us) = tracked.join()?;
1851            let (untracked_count, untracked_profile) = untracked.join()?;
1852            if let Some(profile) = profile.as_mut() {
1853                profile.tracked_elapsed_us = tracked_elapsed_us;
1854            }
1855            Ok((
1856                tracked_count,
1857                untracked_count,
1858                profile_enabled.then_some(untracked_profile),
1859            ))
1860        })?;
1861    if let Some(profile) = profile.as_mut() {
1862        if let Some(untracked_profile) = untracked_profile {
1863            profile.merge_untracked(untracked_profile);
1864        }
1865        profile.emit();
1866    }
1867    Ok(Some(tracked_count + untracked_count))
1868}
1869
1870pub(crate) fn emit_untracked_status_entry<'a, F>(
1871    emit: &'a mut F,
1872) -> impl FnMut(&[u8]) -> Result<StreamControl> + 'a
1873where
1874    F: for<'row> FnMut(ShortStatusRow<'row>) -> Result<StreamControl>,
1875{
1876    |path| emit(untracked_status_row(path))
1877}
1878
1879pub(crate) fn untracked_status_entry(path: Vec<u8>) -> ShortStatusEntry {
1880    ShortStatusEntry {
1881        index: b'?',
1882        worktree: b'?',
1883        path,
1884        head_mode: None,
1885        index_mode: None,
1886        worktree_mode: None,
1887        head_oid: None,
1888        index_oid: None,
1889        submodule: None,
1890    }
1891}
1892
1893pub(crate) fn untracked_status_row(path: &[u8]) -> ShortStatusRow<'_> {
1894    ShortStatusRow {
1895        index: b'?',
1896        worktree: b'?',
1897        path,
1898        head_mode: None,
1899        index_mode: None,
1900        worktree_mode: None,
1901        head_oid: None,
1902        index_oid: None,
1903        submodule: None,
1904    }
1905}
1906
1907pub(crate) fn append_untracked_status_entries(
1908    entries: &mut Vec<ShortStatusEntry>,
1909    untracked_paths: Vec<Vec<u8>>,
1910) {
1911    for path in untracked_paths {
1912        entries.push(untracked_status_entry(path));
1913    }
1914}
1915
1916#[derive(Debug, Clone, Copy)]
1917pub(crate) enum TrackedOnlyPrecheck {
1918    Deleted(usize),
1919    Slow(usize),
1920}
1921
1922#[derive(Debug)]
1923pub(crate) enum TrackedOnlyPrecheckOutcome {
1924    Clean,
1925    Deleted,
1926    Slow,
1927}
1928
1929pub(crate) fn short_status_tracked_only_head_matches_index_parallel(
1930    worktree_root: &Path,
1931    git_dir: &Path,
1932    format: ObjectFormat,
1933    index: &Index,
1934    stat_cache: &IndexStatCache,
1935    sparse_checkout_active: bool,
1936    untracked_mode: StatusUntrackedMode,
1937) -> Result<Vec<ShortStatusEntry>> {
1938    let prechecks = tracked_only_non_clean_prechecks_parallel(
1939        worktree_root,
1940        index,
1941        stat_cache,
1942        sparse_checkout_active,
1943    )?;
1944
1945    let mut clean_filter = None;
1946    let mut entries = Vec::new();
1947    for precheck in prechecks {
1948        match precheck {
1949            TrackedOnlyPrecheck::Deleted(idx) => {
1950                let entry = &index.entries[idx];
1951                if entry.is_intent_to_add() {
1952                    continue;
1953                }
1954                let path = entry.path.as_bytes();
1955                entries.push(ShortStatusEntry {
1956                    index: b' ',
1957                    worktree: b'D',
1958                    path: path.to_vec(),
1959                    head_mode: Some(entry.mode),
1960                    index_mode: Some(entry.mode),
1961                    worktree_mode: None,
1962                    head_oid: Some(entry.oid),
1963                    index_oid: Some(entry.oid),
1964                    submodule: None,
1965                });
1966            }
1967            TrackedOnlyPrecheck::Slow(idx) => {
1968                let entry = &index.entries[idx];
1969                let path = entry.path.as_bytes();
1970                let index_entry = TrackedEntry {
1971                    mode: entry.mode,
1972                    oid: entry.oid,
1973                };
1974                let worktree_entry = worktree_entry_for_index_entry_with_attributes(
1975                    worktree_root,
1976                    git_dir,
1977                    format,
1978                    entry,
1979                    stat_cache,
1980                    &mut clean_filter,
1981                )?;
1982                let submodule = tracked_only_submodule_status(
1983                    worktree_root,
1984                    path,
1985                    &index_entry,
1986                    worktree_entry.as_ref(),
1987                    untracked_mode,
1988                )?;
1989                let worktree_code = match worktree_entry.as_ref() {
1990                    None if entry.is_intent_to_add() => b' ',
1991                    None => b'D',
1992                    Some(_) if entry.is_intent_to_add() => b'A',
1993                    Some(worktree_entry) if *worktree_entry != index_entry => {
1994                        status_change_code(index_entry.mode, worktree_entry.mode)
1995                    }
1996                    _ if submodule.is_some_and(|sub| sub.any()) => b'M',
1997                    _ => b' ',
1998                };
1999                if worktree_code != b' ' {
2000                    entries.push(ShortStatusEntry {
2001                        index: b' ',
2002                        worktree: worktree_code,
2003                        path: path.to_vec(),
2004                        head_mode: (!entry.is_intent_to_add()).then_some(index_entry.mode),
2005                        index_mode: (!entry.is_intent_to_add()).then_some(index_entry.mode),
2006                        worktree_mode: worktree_entry.as_ref().map(|entry| entry.mode),
2007                        head_oid: (!entry.is_intent_to_add()).then_some(index_entry.oid),
2008                        index_oid: (!entry.is_intent_to_add()).then_some(index_entry.oid),
2009                        submodule: submodule.filter(|sub| sub.any()),
2010                    });
2011                }
2012            }
2013        }
2014    }
2015    entries.sort_by(|left, right| {
2016        status_sort_category(left)
2017            .cmp(&status_sort_category(right))
2018            .then_with(|| left.path.cmp(&right.path))
2019    });
2020    Ok(entries)
2021}
2022
2023pub(crate) fn short_status_borrowed_tracked_only_head_matches_index_parallel(
2024    worktree_root: &Path,
2025    git_dir: &Path,
2026    format: ObjectFormat,
2027    index: &BorrowedIndex<'_>,
2028    stat_cache: &IndexStatCache,
2029    sparse_checkout_active: bool,
2030    untracked_mode: StatusUntrackedMode,
2031) -> Result<Vec<ShortStatusEntry>> {
2032    let prechecks = tracked_only_borrowed_non_clean_prechecks_parallel(
2033        worktree_root,
2034        index,
2035        stat_cache,
2036        sparse_checkout_active,
2037    )?;
2038
2039    let mut clean_filter = None;
2040    let mut entries = Vec::new();
2041    for precheck in prechecks {
2042        match precheck {
2043            TrackedOnlyPrecheck::Deleted(idx) => {
2044                let entry = &index.entries[idx];
2045                if entry.is_intent_to_add() {
2046                    continue;
2047                }
2048                entries.push(ShortStatusEntry {
2049                    index: b' ',
2050                    worktree: b'D',
2051                    path: entry.path.to_vec(),
2052                    head_mode: Some(entry.mode),
2053                    index_mode: Some(entry.mode),
2054                    worktree_mode: None,
2055                    head_oid: Some(entry.oid),
2056                    index_oid: Some(entry.oid),
2057                    submodule: None,
2058                });
2059            }
2060            TrackedOnlyPrecheck::Slow(idx) => {
2061                let entry = &index.entries[idx];
2062                let index_entry = TrackedEntry {
2063                    mode: entry.mode,
2064                    oid: entry.oid,
2065                };
2066                let worktree_entry = worktree_entry_for_index_entry_ref_with_attributes(
2067                    worktree_root,
2068                    git_dir,
2069                    format,
2070                    entry,
2071                    stat_cache,
2072                    &mut clean_filter,
2073                )?;
2074                let submodule = tracked_only_submodule_status(
2075                    worktree_root,
2076                    entry.path,
2077                    &index_entry,
2078                    worktree_entry.as_ref(),
2079                    untracked_mode,
2080                )?;
2081                let worktree_code = match worktree_entry.as_ref() {
2082                    None if entry.is_intent_to_add() => b' ',
2083                    None => b'D',
2084                    Some(_) if entry.is_intent_to_add() => b'A',
2085                    Some(worktree_entry) if *worktree_entry != index_entry => {
2086                        status_change_code(index_entry.mode, worktree_entry.mode)
2087                    }
2088                    _ if submodule.is_some_and(|sub| sub.any()) => b'M',
2089                    _ => b' ',
2090                };
2091                if worktree_code != b' ' {
2092                    entries.push(ShortStatusEntry {
2093                        index: b' ',
2094                        worktree: worktree_code,
2095                        path: entry.path.to_vec(),
2096                        head_mode: (!entry.is_intent_to_add()).then_some(index_entry.mode),
2097                        index_mode: (!entry.is_intent_to_add()).then_some(index_entry.mode),
2098                        worktree_mode: worktree_entry.as_ref().map(|entry| entry.mode),
2099                        head_oid: (!entry.is_intent_to_add()).then_some(index_entry.oid),
2100                        index_oid: (!entry.is_intent_to_add()).then_some(index_entry.oid),
2101                        submodule: submodule.filter(|sub| sub.any()),
2102                    });
2103                }
2104            }
2105        }
2106    }
2107    entries.sort_by(|left, right| {
2108        status_sort_category(left)
2109            .cmp(&status_sort_category(right))
2110            .then_with(|| left.path.cmp(&right.path))
2111    });
2112    Ok(entries)
2113}
2114
2115pub(crate) fn stream_short_status_borrowed_tracked_only_head_matches_index_parallel<F>(
2116    worktree_root: &Path,
2117    git_dir: &Path,
2118    format: ObjectFormat,
2119    index: &BorrowedIndex<'_>,
2120    stat_cache: &IndexStatCache,
2121    sparse_checkout_active: bool,
2122    untracked_mode: StatusUntrackedMode,
2123    emit: &mut F,
2124) -> Result<StreamControl>
2125where
2126    F: for<'a> FnMut(ShortStatusRow<'a>) -> Result<StreamControl>,
2127{
2128    let prechecks = tracked_only_borrowed_non_clean_prechecks_parallel(
2129        worktree_root,
2130        index,
2131        stat_cache,
2132        sparse_checkout_active,
2133    )?;
2134
2135    let mut clean_filter = None;
2136    for precheck in prechecks {
2137        match precheck {
2138            TrackedOnlyPrecheck::Deleted(idx) => {
2139                let entry = &index.entries[idx];
2140                if entry.is_intent_to_add() {
2141                    continue;
2142                }
2143                if emit(ShortStatusRow {
2144                    index: b' ',
2145                    worktree: b'D',
2146                    path: entry.path,
2147                    head_mode: Some(entry.mode),
2148                    index_mode: Some(entry.mode),
2149                    worktree_mode: None,
2150                    head_oid: Some(entry.oid),
2151                    index_oid: Some(entry.oid),
2152                    submodule: None,
2153                })?
2154                .is_stop()
2155                {
2156                    return Ok(StreamControl::Stop);
2157                }
2158            }
2159            TrackedOnlyPrecheck::Slow(idx) => {
2160                let entry = &index.entries[idx];
2161                let index_entry = TrackedEntry {
2162                    mode: entry.mode,
2163                    oid: entry.oid,
2164                };
2165                let worktree_entry = worktree_entry_for_index_entry_ref_with_attributes(
2166                    worktree_root,
2167                    git_dir,
2168                    format,
2169                    entry,
2170                    stat_cache,
2171                    &mut clean_filter,
2172                )?;
2173                let submodule = tracked_only_submodule_status(
2174                    worktree_root,
2175                    entry.path,
2176                    &index_entry,
2177                    worktree_entry.as_ref(),
2178                    untracked_mode,
2179                )?;
2180                let worktree_code = match worktree_entry.as_ref() {
2181                    None if entry.is_intent_to_add() => b' ',
2182                    None => b'D',
2183                    Some(_) if entry.is_intent_to_add() => b'A',
2184                    Some(worktree_entry) if *worktree_entry != index_entry => {
2185                        status_change_code(index_entry.mode, worktree_entry.mode)
2186                    }
2187                    _ if submodule.is_some_and(|sub| sub.any()) => b'M',
2188                    _ => b' ',
2189                };
2190                if worktree_code != b' ' {
2191                    if emit(ShortStatusRow {
2192                        index: b' ',
2193                        worktree: worktree_code,
2194                        path: entry.path,
2195                        head_mode: (!entry.is_intent_to_add()).then_some(index_entry.mode),
2196                        index_mode: (!entry.is_intent_to_add()).then_some(index_entry.mode),
2197                        worktree_mode: worktree_entry.as_ref().map(|entry| entry.mode),
2198                        head_oid: (!entry.is_intent_to_add()).then_some(index_entry.oid),
2199                        index_oid: (!entry.is_intent_to_add()).then_some(index_entry.oid),
2200                        submodule: submodule.filter(|sub| sub.any()),
2201                    })?
2202                    .is_stop()
2203                    {
2204                        return Ok(StreamControl::Stop);
2205                    }
2206                }
2207            }
2208        }
2209    }
2210    Ok(StreamControl::Continue)
2211}
2212
2213pub(crate) fn short_status_borrowed_tracked_only_head_matches_index_count_parallel(
2214    worktree_root: &Path,
2215    git_dir: &Path,
2216    format: ObjectFormat,
2217    index: &BorrowedIndex<'_>,
2218    stat_cache: &IndexStatCache,
2219    sparse_checkout_active: bool,
2220    untracked_mode: StatusUntrackedMode,
2221) -> Result<usize> {
2222    let prechecks = tracked_only_borrowed_non_clean_prechecks_parallel(
2223        worktree_root,
2224        index,
2225        stat_cache,
2226        sparse_checkout_active,
2227    )?;
2228
2229    let mut clean_filter = None;
2230    let mut count = 0usize;
2231    for precheck in prechecks {
2232        match precheck {
2233            TrackedOnlyPrecheck::Deleted(_) => count += 1,
2234            TrackedOnlyPrecheck::Slow(idx) => {
2235                let entry = &index.entries[idx];
2236                let index_entry = TrackedEntry {
2237                    mode: entry.mode,
2238                    oid: entry.oid,
2239                };
2240                let worktree_entry = worktree_entry_for_index_entry_ref_with_attributes(
2241                    worktree_root,
2242                    git_dir,
2243                    format,
2244                    entry,
2245                    stat_cache,
2246                    &mut clean_filter,
2247                )?;
2248                let submodule = tracked_only_submodule_status(
2249                    worktree_root,
2250                    entry.path,
2251                    &index_entry,
2252                    worktree_entry.as_ref(),
2253                    untracked_mode,
2254                )?;
2255                let worktree_code = match worktree_entry.as_ref() {
2256                    None => b'D',
2257                    Some(worktree_entry) if *worktree_entry != index_entry => {
2258                        status_change_code(index_entry.mode, worktree_entry.mode)
2259                    }
2260                    _ if submodule.is_some_and(|sub| sub.any()) => b'M',
2261                    _ => b' ',
2262                };
2263                if worktree_code != b' ' {
2264                    count += 1;
2265                }
2266            }
2267        }
2268    }
2269    Ok(count)
2270}
2271
2272pub(crate) fn short_status_tracked_only_with_head_parallel(
2273    worktree_root: &Path,
2274    git_dir: &Path,
2275    format: ObjectFormat,
2276    index: &Index,
2277    stat_cache: &IndexStatCache,
2278    head: &BTreeMap<Vec<u8>, TrackedEntry>,
2279    sparse_checkout_active: bool,
2280    untracked_mode: StatusUntrackedMode,
2281) -> Result<Vec<ShortStatusEntry>> {
2282    let prechecks = tracked_only_non_clean_prechecks_parallel(
2283        worktree_root,
2284        index,
2285        stat_cache,
2286        sparse_checkout_active,
2287    )?;
2288    let mut precheck_cursor = 0usize;
2289    let mut clean_filter = None;
2290    let mut entries = Vec::new();
2291
2292    for (idx, entry) in index.entries.iter().enumerate() {
2293        if entry.stage() != Stage::Normal {
2294            continue;
2295        }
2296        let path = entry.path.as_bytes();
2297        let index_entry = TrackedEntry {
2298            mode: entry.mode,
2299            oid: entry.oid,
2300        };
2301        let head_entry = head.get(path);
2302        let visible_index_entry = (!entry.is_intent_to_add()).then_some(&index_entry);
2303        let index_code = match (head_entry, visible_index_entry) {
2304            (None, Some(_)) => b'A',
2305            (Some(_), None) => b'D',
2306            (Some(head_entry), Some(index_entry)) if *head_entry != *index_entry => {
2307                status_change_code(head_entry.mode, index_entry.mode)
2308            }
2309            _ => b' ',
2310        };
2311        let precheck = prechecks
2312            .get(precheck_cursor)
2313            .copied()
2314            .and_then(|precheck| {
2315                if tracked_only_precheck_index(precheck) == idx {
2316                    precheck_cursor += 1;
2317                    Some(precheck)
2318                } else {
2319                    None
2320                }
2321            });
2322        let (worktree_code, worktree_mode, submodule) = match precheck {
2323            None if entry.is_intent_to_add() => (b' ', None, None),
2324            None => (b' ', Some(index_entry.mode), None),
2325            Some(TrackedOnlyPrecheck::Deleted(_)) if entry.is_intent_to_add() => (b' ', None, None),
2326            Some(TrackedOnlyPrecheck::Deleted(_)) => (b'D', None, None),
2327            Some(TrackedOnlyPrecheck::Slow(_)) => {
2328                let worktree_entry = worktree_entry_for_index_entry_with_attributes(
2329                    worktree_root,
2330                    git_dir,
2331                    format,
2332                    entry,
2333                    stat_cache,
2334                    &mut clean_filter,
2335                )?;
2336                let submodule = tracked_only_submodule_status(
2337                    worktree_root,
2338                    path,
2339                    &index_entry,
2340                    worktree_entry.as_ref(),
2341                    untracked_mode,
2342                )?;
2343                let worktree_code = match worktree_entry.as_ref() {
2344                    None if entry.is_intent_to_add() => b' ',
2345                    None => b'D',
2346                    Some(_) if entry.is_intent_to_add() => b'A',
2347                    Some(worktree_entry) if *worktree_entry != index_entry => {
2348                        status_change_code(index_entry.mode, worktree_entry.mode)
2349                    }
2350                    _ if submodule.is_some_and(|sub| sub.any()) => b'M',
2351                    _ => b' ',
2352                };
2353                (
2354                    worktree_code,
2355                    worktree_entry.as_ref().map(|entry| entry.mode),
2356                    submodule.filter(|sub| sub.any()),
2357                )
2358            }
2359        };
2360        if index_code != b' ' || worktree_code != b' ' {
2361            entries.push(ShortStatusEntry {
2362                index: index_code,
2363                worktree: worktree_code,
2364                path: path.to_vec(),
2365                head_mode: head_entry.map(|entry| entry.mode),
2366                index_mode: visible_index_entry.map(|entry| entry.mode),
2367                worktree_mode,
2368                head_oid: head_entry.map(|entry| entry.oid),
2369                index_oid: visible_index_entry.map(|entry| entry.oid),
2370                submodule,
2371            });
2372        }
2373    }
2374
2375    let index_paths = index
2376        .entries
2377        .iter()
2378        .filter(|entry| entry.stage() == Stage::Normal)
2379        .map(|entry| entry.path.as_bytes().to_vec())
2380        .collect::<HashSet<_>>();
2381    for (path, head_entry) in head {
2382        if index_paths.contains(path.as_slice()) {
2383            continue;
2384        }
2385        entries.push(ShortStatusEntry {
2386            index: b'D',
2387            worktree: b' ',
2388            path: path.clone(),
2389            head_mode: Some(head_entry.mode),
2390            index_mode: None,
2391            worktree_mode: None,
2392            head_oid: Some(head_entry.oid),
2393            index_oid: None,
2394            submodule: None,
2395        });
2396    }
2397    entries.sort_by(|left, right| {
2398        status_sort_category(left)
2399            .cmp(&status_sort_category(right))
2400            .then_with(|| left.path.cmp(&right.path))
2401    });
2402    Ok(entries)
2403}
2404
2405pub(crate) fn tracked_only_precheck_index(precheck: TrackedOnlyPrecheck) -> usize {
2406    match precheck {
2407        TrackedOnlyPrecheck::Deleted(idx) | TrackedOnlyPrecheck::Slow(idx) => idx,
2408    }
2409}
2410
2411pub(crate) fn stage0_index_entry_count<E>(
2412    entries: &[E],
2413    mut stage: impl FnMut(&E) -> Stage,
2414) -> usize {
2415    entries
2416        .iter()
2417        .filter(|entry| stage(entry) == Stage::Normal)
2418        .count()
2419}
2420
2421pub(crate) fn stage0_index_chunk_ranges<E>(
2422    entries: &[E],
2423    chunk_size: usize,
2424    mut stage: impl FnMut(&E) -> Stage,
2425) -> Vec<std::ops::Range<usize>> {
2426    debug_assert!(chunk_size > 0);
2427    let mut ranges = Vec::new();
2428    let mut start = None;
2429    let mut end = 0usize;
2430    let mut normals_in_chunk = 0usize;
2431    for (idx, entry) in entries.iter().enumerate() {
2432        if stage(entry) != Stage::Normal {
2433            continue;
2434        }
2435        if start.is_none() {
2436            start = Some(idx);
2437        }
2438        end = idx + 1;
2439        normals_in_chunk += 1;
2440        if normals_in_chunk == chunk_size {
2441            ranges.push(start.expect("chunk start must exist")..end);
2442            start = None;
2443            normals_in_chunk = 0;
2444        }
2445    }
2446    if let Some(start) = start {
2447        ranges.push(start..end);
2448    }
2449    ranges
2450}
2451
2452pub(crate) fn tracked_only_non_clean_prechecks_parallel(
2453    worktree_root: &Path,
2454    index: &Index,
2455    stat_cache: &IndexStatCache,
2456    sparse_checkout_active: bool,
2457) -> Result<Vec<TrackedOnlyPrecheck>> {
2458    let normal_count = stage0_index_entry_count(&index.entries, IndexEntry::stage);
2459    if normal_count == 0 {
2460        return Ok(Vec::new());
2461    }
2462    let executor = StatusExecutor::new();
2463    let worker_count = executor.worker_count_for(normal_count, 512, 4);
2464    if worker_count == 1 {
2465        let mut prechecks = Vec::new();
2466        let mut absolute = PathBuf::new();
2467        for (idx, entry) in index.entries.iter().enumerate() {
2468            if entry.stage() != Stage::Normal {
2469                continue;
2470            }
2471            match tracked_only_stat_precheck(
2472                worktree_root,
2473                entry,
2474                stat_cache,
2475                sparse_checkout_active,
2476                &mut absolute,
2477            )? {
2478                TrackedOnlyPrecheckOutcome::Clean => {}
2479                TrackedOnlyPrecheckOutcome::Deleted => {
2480                    prechecks.push(TrackedOnlyPrecheck::Deleted(idx));
2481                }
2482                TrackedOnlyPrecheckOutcome::Slow => {
2483                    prechecks.push(TrackedOnlyPrecheck::Slow(idx));
2484                }
2485            }
2486        }
2487        return Ok(prechecks);
2488    }
2489    let chunk_size = normal_count.div_ceil(worker_count);
2490    let chunk_ranges = stage0_index_chunk_ranges(&index.entries, chunk_size, IndexEntry::stage);
2491    let next_chunk = AtomicUsize::new(0);
2492    let mut prechecks = std::thread::scope(|scope| -> Result<Vec<TrackedOnlyPrecheck>> {
2493        let mut handles = Vec::new();
2494        for _ in 0..worker_count {
2495            let chunk_ranges = &chunk_ranges;
2496            let next_chunk = &next_chunk;
2497            handles.push(executor.spawn(
2498                scope,
2499                "status-precheck",
2500                move || -> Result<Vec<TrackedOnlyPrecheck>> {
2501                    let mut prechecks = Vec::new();
2502                    let mut absolute = PathBuf::new();
2503                    loop {
2504                        let chunk_idx = next_chunk.fetch_add(1, Ordering::Relaxed);
2505                        let Some(range) = chunk_ranges.get(chunk_idx) else {
2506                            break;
2507                        };
2508                        for idx in range.clone() {
2509                            let entry = &index.entries[idx];
2510                            if entry.stage() != Stage::Normal {
2511                                continue;
2512                            }
2513                            match tracked_only_stat_precheck(
2514                                worktree_root,
2515                                entry,
2516                                stat_cache,
2517                                sparse_checkout_active,
2518                                &mut absolute,
2519                            )? {
2520                                TrackedOnlyPrecheckOutcome::Clean => {}
2521                                TrackedOnlyPrecheckOutcome::Deleted => {
2522                                    prechecks.push(TrackedOnlyPrecheck::Deleted(idx));
2523                                }
2524                                TrackedOnlyPrecheckOutcome::Slow => {
2525                                    prechecks.push(TrackedOnlyPrecheck::Slow(idx));
2526                                }
2527                            }
2528                        }
2529                    }
2530                    Ok(prechecks)
2531                },
2532            )?);
2533        }
2534        let mut prechecks = Vec::new();
2535        for handle in handles {
2536            let mut chunk = handle.join()?;
2537            prechecks.append(&mut chunk);
2538        }
2539        Ok(prechecks)
2540    })?;
2541    prechecks.sort_by_key(|precheck| tracked_only_precheck_index(*precheck));
2542    Ok(prechecks)
2543}
2544
2545pub(crate) fn tracked_only_borrowed_non_clean_prechecks_parallel(
2546    worktree_root: &Path,
2547    index: &BorrowedIndex<'_>,
2548    stat_cache: &IndexStatCache,
2549    sparse_checkout_active: bool,
2550) -> Result<Vec<TrackedOnlyPrecheck>> {
2551    let normal_count = stage0_index_entry_count(&index.entries, IndexEntryRef::stage);
2552    if normal_count == 0 {
2553        return Ok(Vec::new());
2554    }
2555    let executor = StatusExecutor::new();
2556    let worker_count = executor.worker_count_for(normal_count, 512, 4);
2557    if worker_count == 1 {
2558        let mut prechecks = Vec::new();
2559        let mut absolute = PathBuf::new();
2560        for (idx, entry) in index.entries.iter().enumerate() {
2561            if entry.stage() != Stage::Normal {
2562                continue;
2563            }
2564            match tracked_only_borrowed_stat_precheck(
2565                worktree_root,
2566                entry,
2567                stat_cache,
2568                sparse_checkout_active,
2569                &mut absolute,
2570            )? {
2571                TrackedOnlyPrecheckOutcome::Clean => {}
2572                TrackedOnlyPrecheckOutcome::Deleted => {
2573                    prechecks.push(TrackedOnlyPrecheck::Deleted(idx));
2574                }
2575                TrackedOnlyPrecheckOutcome::Slow => {
2576                    prechecks.push(TrackedOnlyPrecheck::Slow(idx));
2577                }
2578            }
2579        }
2580        return Ok(prechecks);
2581    }
2582    let chunk_size = normal_count.div_ceil(worker_count);
2583    let chunk_ranges = stage0_index_chunk_ranges(&index.entries, chunk_size, IndexEntryRef::stage);
2584    let next_chunk = AtomicUsize::new(0);
2585    let mut prechecks = std::thread::scope(|scope| -> Result<Vec<TrackedOnlyPrecheck>> {
2586        let mut handles = Vec::new();
2587        for _ in 0..worker_count {
2588            let chunk_ranges = &chunk_ranges;
2589            let next_chunk = &next_chunk;
2590            handles.push(executor.spawn(
2591                scope,
2592                "status-precheck",
2593                move || -> Result<Vec<TrackedOnlyPrecheck>> {
2594                    let mut prechecks = Vec::new();
2595                    let mut absolute = PathBuf::new();
2596                    loop {
2597                        let chunk_idx = next_chunk.fetch_add(1, Ordering::Relaxed);
2598                        let Some(range) = chunk_ranges.get(chunk_idx) else {
2599                            break;
2600                        };
2601                        for idx in range.clone() {
2602                            let entry = &index.entries[idx];
2603                            if entry.stage() != Stage::Normal {
2604                                continue;
2605                            }
2606                            match tracked_only_borrowed_stat_precheck(
2607                                worktree_root,
2608                                entry,
2609                                stat_cache,
2610                                sparse_checkout_active,
2611                                &mut absolute,
2612                            )? {
2613                                TrackedOnlyPrecheckOutcome::Clean => {}
2614                                TrackedOnlyPrecheckOutcome::Deleted => {
2615                                    prechecks.push(TrackedOnlyPrecheck::Deleted(idx));
2616                                }
2617                                TrackedOnlyPrecheckOutcome::Slow => {
2618                                    prechecks.push(TrackedOnlyPrecheck::Slow(idx));
2619                                }
2620                            }
2621                        }
2622                    }
2623                    Ok(prechecks)
2624                },
2625            )?);
2626        }
2627        let mut prechecks = Vec::new();
2628        for handle in handles {
2629            let mut chunk = handle.join()?;
2630            prechecks.append(&mut chunk);
2631        }
2632        Ok(prechecks)
2633    })?;
2634    prechecks.sort_by_key(|precheck| tracked_only_precheck_index(*precheck));
2635    Ok(prechecks)
2636}
2637
2638pub(crate) fn tracked_only_stat_precheck(
2639    worktree_root: &Path,
2640    index_entry: &IndexEntry,
2641    stat_cache: &IndexStatCache,
2642    sparse_checkout_active: bool,
2643    absolute: &mut PathBuf,
2644) -> Result<TrackedOnlyPrecheckOutcome> {
2645    // Skip-worktree handling mirrors git's clear_skip_worktree_from_present_files:
2646    // when core.sparseCheckout is DISABLED the bit is honored literally, so the
2647    // worktree side is always clean regardless of the on-disk file (git's
2648    // run_diff_files skips ce_skip_worktree entries). When it is ENABLED, git
2649    // clears the bit for entries whose file is present, so a present file's
2650    // worktree changes ARE reported; only an absent file stays clean.
2651    if index_entry.is_skip_worktree() && !sparse_checkout_active {
2652        return Ok(TrackedOnlyPrecheckOutcome::Clean);
2653    }
2654    if sley_index::is_gitlink(index_entry.mode) {
2655        return Ok(TrackedOnlyPrecheckOutcome::Slow);
2656    }
2657    let git_path = index_entry.path.as_bytes();
2658    set_worktree_path_from_repo_path(worktree_root, git_path, absolute)?;
2659    let metadata = match fs::symlink_metadata(&absolute) {
2660        Ok(metadata) => metadata,
2661        Err(err)
2662            if matches!(
2663                err.kind(),
2664                std::io::ErrorKind::NotFound | std::io::ErrorKind::NotADirectory
2665            ) =>
2666        {
2667            // sparse-active + absent skip-worktree: bit stays set -> clean.
2668            if index_entry.is_skip_worktree() {
2669                return Ok(TrackedOnlyPrecheckOutcome::Clean);
2670            }
2671            return Ok(TrackedOnlyPrecheckOutcome::Deleted);
2672        }
2673        Err(err) => return Err(err.into()),
2674    };
2675    let file_type = metadata.file_type();
2676    if file_type.is_dir() || !(file_type.is_file() || file_type.is_symlink()) {
2677        return Ok(TrackedOnlyPrecheckOutcome::Slow);
2678    }
2679    if stat_cache
2680        .reuse_index_entry(index_entry, &metadata)
2681        .is_some()
2682    {
2683        Ok(TrackedOnlyPrecheckOutcome::Clean)
2684    } else {
2685        Ok(TrackedOnlyPrecheckOutcome::Slow)
2686    }
2687}
2688
2689pub(crate) fn tracked_only_borrowed_stat_precheck(
2690    worktree_root: &Path,
2691    index_entry: &IndexEntryRef<'_>,
2692    stat_cache: &IndexStatCache,
2693    sparse_checkout_active: bool,
2694    absolute: &mut PathBuf,
2695) -> Result<TrackedOnlyPrecheckOutcome> {
2696    // See tracked_only_stat_precheck: when core.sparseCheckout is disabled the
2697    // skip-worktree bit is honored literally (worktree side always clean); when
2698    // enabled, git clears the bit for present files so their changes ARE
2699    // reported, and only absent files stay clean.
2700    if index_entry.is_skip_worktree() && !sparse_checkout_active {
2701        return Ok(TrackedOnlyPrecheckOutcome::Clean);
2702    }
2703    if sley_index::is_gitlink(index_entry.mode) {
2704        return Ok(TrackedOnlyPrecheckOutcome::Slow);
2705    }
2706    set_worktree_path_from_repo_path(worktree_root, index_entry.path, absolute)?;
2707    let metadata = match fs::symlink_metadata(&absolute) {
2708        Ok(metadata) => metadata,
2709        Err(err)
2710            if matches!(
2711                err.kind(),
2712                std::io::ErrorKind::NotFound | std::io::ErrorKind::NotADirectory
2713            ) =>
2714        {
2715            // sparse-active + absent skip-worktree: bit stays set -> clean.
2716            if index_entry.is_skip_worktree() {
2717                return Ok(TrackedOnlyPrecheckOutcome::Clean);
2718            }
2719            return Ok(TrackedOnlyPrecheckOutcome::Deleted);
2720        }
2721        Err(err) => return Err(err.into()),
2722    };
2723    let file_type = metadata.file_type();
2724    if file_type.is_dir() || !(file_type.is_file() || file_type.is_symlink()) {
2725        return Ok(TrackedOnlyPrecheckOutcome::Slow);
2726    }
2727    if stat_cache
2728        .reuse_index_entry_ref(index_entry, &metadata)
2729        .is_some()
2730    {
2731        Ok(TrackedOnlyPrecheckOutcome::Clean)
2732    } else {
2733        Ok(TrackedOnlyPrecheckOutcome::Slow)
2734    }
2735}
2736
2737pub(crate) fn set_worktree_path_from_repo_path(
2738    worktree_root: &Path,
2739    git_path: &[u8],
2740    out: &mut PathBuf,
2741) -> Result<()> {
2742    out.clear();
2743    out.push(worktree_root);
2744    push_repo_path(out, git_path)
2745}
2746
2747#[cfg(unix)]
2748pub(crate) fn push_repo_path(out: &mut PathBuf, path: &[u8]) -> Result<()> {
2749    use std::os::unix::ffi::OsStrExt;
2750
2751    out.push(Path::new(std::ffi::OsStr::from_bytes(path)));
2752    Ok(())
2753}
2754
2755#[cfg(not(unix))]
2756pub(crate) fn push_repo_path(out: &mut PathBuf, path: &[u8]) -> Result<()> {
2757    let path = std::str::from_utf8(path)
2758        .map_err(|_| GitError::InvalidPath("index path is not utf8".into()))?;
2759    for component in path.split('/') {
2760        out.push(component);
2761    }
2762    Ok(())
2763}
2764
2765pub(crate) fn tracked_only_submodule_status(
2766    worktree_root: &Path,
2767    path: &[u8],
2768    index_entry: &TrackedEntry,
2769    worktree_entry: Option<&TrackedEntry>,
2770    _untracked_mode: StatusUntrackedMode,
2771) -> Result<Option<SubmoduleStatus>> {
2772    let Some(worktree_entry) = worktree_entry else {
2773        return Ok(None);
2774    };
2775    if !sley_index::is_gitlink(index_entry.mode) || !sley_index::is_gitlink(worktree_entry.mode) {
2776        return Ok(None);
2777    }
2778    let absolute = worktree_root.join(repo_path_to_os_path(path)?);
2779    let dirt = if absolute.is_dir() {
2780        submodule_dirt_checked(&absolute)?
2781    } else {
2782        0
2783    };
2784    Ok(Some(SubmoduleStatus {
2785        new_commits: index_entry.oid != worktree_entry.oid,
2786        modified_content: dirt & DIRTY_SUBMODULE_MODIFIED != 0,
2787        untracked_content: dirt & DIRTY_SUBMODULE_UNTRACKED != 0,
2788    }))
2789}
2790
2791pub(crate) fn status_sort_category(entry: &ShortStatusEntry) -> u8 {
2792    match (entry.index, entry.worktree) {
2793        (b'?', b'?') => 1,
2794        (b'!', b'!') => 2,
2795        _ => 0,
2796    }
2797}