Skip to main content

differential_engine/
worktree.rs

1//! Tree snapshots of uncommitted state (ADR 0017): the index and the worktree
2//! as plain tree oids, so the rest of the pipeline — diff-tree enumeration,
3//! the invariants, blob reads — runs on them unchanged.
4//!
5//! Plumbing only, and never the user's index: a temporary `GIT_INDEX_FILE` is
6//! seeded from `ls-files -s -z` piped into `update-index -z --index-info`.
7//! Snapshot blobs land in the odb (unreferenced, gc-able), which pins the
8//! content so later `cat-file` reads by `<tree>:<path>` always resolve.
9
10use crate::EngineError;
11use crate::ports::{IndexSession, TreeBuilder, WorkingCopy};
12
13/// Tree oid of the current index (the staged state). The user's index file is
14/// never written to.
15pub fn index_tree<G: TreeBuilder>(git: &G) -> Result<String, EngineError> {
16    git.begin_from_current_index()?.write_tree()
17}
18
19/// Tree oid of the worktree: every tracked file's current content plus
20/// untracked-but-not-ignored files, with worktree deletions honoured.
21pub fn worktree_tree<G>(git: &G) -> Result<String, EngineError>
22where
23    G: TreeBuilder + WorkingCopy,
24{
25    let mut session = git.begin_from_current_index()?;
26    // Union of tracked + untracked-unignored paths, NUL-delimited: `--add`
27    // admits new files, `--remove` drops ones deleted from the worktree.
28    let mut paths = git.tracked_paths()?;
29    paths.extend_from_slice(&git.untracked_paths()?);
30    session.stage_from_worktree(&paths)?;
31    session.write_tree()
32}
33
34/// Whether a worktree snapshot would differ from `HEAD` at all.
35///
36/// The question a reviewer surface actually has: "is offering to include
37/// uncommitted work going to change anything?" Answered with two cheap
38/// plumbing calls rather than by building the snapshot and comparing trees —
39/// the snapshot re-hashes every tracked file, which is precisely the work a
40/// clean answer lets the caller skip.
41pub fn is_clean<G: WorkingCopy>(git: &G) -> Result<bool, EngineError> {
42    Ok(!git.has_tracked_changes()? && git.untracked_paths()?.is_empty())
43}