Skip to main content

differential_engine/
tree.rs

1//! Build the final tree from applied hunks, via plumbing against a temporary
2//! index (ADR 0011). No checkout, no touching the user's index or worktree.
3//!
4//! Text file content is always computed BY APPLYING HUNKS — never by copying
5//! head blobs — so tree equality against `head^{tree}` is a real assertion
6//! (invariant 3). The two exceptions, both explicit here: binary files are
7//! staged from the recorded head oid (they carry no hunks; documented
8//! tautology), and submodules are staged as gitlinks from the pseudo-hunk's
9//! commit id.
10
11use crate::EngineError;
12use crate::apply::apply_hunks;
13use crate::model::{DiffView, Hunk};
14use crate::plan;
15use crate::ports::{IndexEntry, IndexSession, ObjectReader, ObjectWriter, TreeBuilder};
16
17/// Stage every file's final state on top of `base` and return the written tree.
18pub fn build_tree<G>(git: &G, base: &str, view: &DiffView) -> Result<String, EngineError>
19where
20    G: ObjectReader + ObjectWriter + TreeBuilder,
21{
22    let mut session = git.begin_from_tree(base)?;
23    // One batch: quoting-proof, and one subprocess instead of one per file.
24    let mut entries = Vec::with_capacity(view.files.len());
25    for f in &view.files {
26        entries.push(staging_entry(git, base, view, f)?);
27    }
28    session.stage(&entries)?;
29    session.write_tree()
30}
31
32/// Compute one file's index record: decide with `plan::final_state`, then
33/// perform whatever that decision implies.
34fn staging_entry<G>(
35    git: &G,
36    base: &str,
37    view: &DiffView,
38    f: &crate::model::FileChange,
39) -> Result<IndexEntry, EngineError>
40where
41    G: ObjectReader + ObjectWriter,
42{
43    IndexEntry::from_staged(plan::final_state(f)?, f.path.clone(), || {
44        let hunks: Vec<&Hunk> = f.hunks.iter().map(|&i| &view.hunks[i]).collect();
45        let base_content = git.blob(base, &f.path)?;
46        let content = apply_hunks(base_content.as_deref(), &hunks);
47        git.write_blob(&content)
48    })
49}