grit_lib/diff/handle.rs
1//! Repository-scoped tree and index diffs via [`Diff`].
2use crate::diff::targets::{collect_files_for_target, limit_target_entries, stored_target_entries};
3use crate::diff::{
4 diff_index_to_tree, diff_index_to_worktree, diff_trees, diff_trees_with_renames_and_policy,
5 DiffEntry, DiffOptions,
6};
7use crate::diff::{DiffTarget, FileDiff, UncommittedDiff};
8use crate::error::Result;
9use crate::index::Index;
10use crate::objects::ObjectId;
11use crate::repo::Repository;
12
13/// Compare trees, the index, and the working tree for one repository.
14#[derive(Debug, Copy, Clone)]
15pub struct Diff<'repo> {
16 repo: &'repo Repository,
17}
18
19impl<'repo> Diff<'repo> {
20 /// Bind diff helpers to `repo`.
21 #[inline]
22 #[must_use]
23 pub fn new(repo: &'repo Repository) -> Self {
24 Self { repo }
25 }
26
27 /// Underlying repository.
28 #[inline]
29 #[must_use]
30 pub fn repository(&self) -> &'repo Repository {
31 self.repo
32 }
33
34 /// Diff two tree oids (either side may be `None` for an empty tree).
35 ///
36 /// Reads tree metadata without loading blobs and skips identical subtree ids.
37 /// The walk retains active directory frames alongside the ODB's bounded tree
38 /// cache; the returned vector owns metadata for every changed path.
39 ///
40 /// # Errors
41 ///
42 /// Propagates ODB read and tree walk errors.
43 ///
44 /// # Examples
45 ///
46 /// See the library guide on [diffing](https://grit-scm.com/docs/library/diff/).
47 #[inline]
48 pub fn tree_to_tree(
49 &self,
50 old_tree: Option<&ObjectId>,
51 new_tree: Option<&ObjectId>,
52 ) -> Result<Vec<DiffEntry>> {
53 diff_trees(self.repo.odb(), old_tree, new_tree, "")
54 }
55
56 /// Like [`Self::tree_to_tree`], then pair renames using `options.renames` (Git `-M`).
57 ///
58 /// # Errors
59 ///
60 /// Propagates ODB read, tree walk, and rename detection errors.
61 pub fn tree_to_tree_renames(
62 &self,
63 old_tree: Option<&ObjectId>,
64 new_tree: Option<&ObjectId>,
65 options: &DiffOptions,
66 ) -> Result<Vec<DiffEntry>> {
67 let config = self.repo.config()?;
68 diff_trees_with_renames_and_policy(
69 self.repo.odb(),
70 old_tree,
71 new_tree,
72 "",
73 options.rename_threshold_u32(),
74 config.as_ref(),
75 &self.repo.thread_policy(),
76 )
77 }
78
79 /// Diff the index against a tree (typically `HEAD`'s tree).
80 ///
81 /// # Errors
82 ///
83 /// Propagates ODB and index diff errors.
84 #[inline]
85 pub fn index_to_tree(
86 &self,
87 index: &Index,
88 tree_oid: Option<&ObjectId>,
89 ignore_submodules: bool,
90 ) -> Result<Vec<DiffEntry>> {
91 diff_index_to_tree(self.repo.odb(), index, tree_oid, ignore_submodules)
92 }
93
94 /// Diff the working tree against the index.
95 ///
96 /// # Errors
97 ///
98 /// Returns an error when the repository has no work tree or diff I/O fails.
99 #[inline]
100 pub fn worktree_to_index(
101 &self,
102 index: &mut Index,
103 ignore_submodule_untracked: bool,
104 simplify_gitlinks: bool,
105 ) -> Result<Vec<DiffEntry>> {
106 let work_tree = self
107 .repo
108 .work_tree()
109 .ok_or(crate::error::Error::RequiresWorkTree)?;
110 diff_index_to_worktree(
111 self.repo.odb(),
112 index,
113 work_tree,
114 ignore_submodule_untracked,
115 simplify_gitlinks,
116 )
117 }
118
119 /// Staged, unstaged, and working-tree diffs over the repository's on-disk index.
120 ///
121 /// # Errors
122 ///
123 /// Returns index read errors.
124 pub fn uncommitted(&self) -> Result<UncommittedDiff<'repo>> {
125 let index = self.repo.index_handle().load_preserving_sparse()?;
126 Ok(UncommittedDiff::new(self.repo, index))
127 }
128
129 /// Changed paths for `target`, limited to `pathspecs` and to the statuses in
130 /// `options.filter` (`--diff-filter`), sorted by path. No contents are loaded.
131 ///
132 /// Commit and tree targets diff stored trees without reading the index. Targets that read
133 /// the index or working tree load it once here; when their contents are needed next, call
134 /// [`UncommittedDiff::entries`] on [`Self::uncommitted`] instead and load them with
135 /// [`UncommittedDiff::load_worktree_changes`] or
136 /// [`load_object_changes`](crate::diff::load_object_changes) on the same handle.
137 ///
138 /// # Parameters
139 ///
140 /// - `target` - what to compare, e.g. from [`resolve_diff_request`](crate::diff::resolve_diff_request).
141 /// - `pathspecs` - repository-relative pathspecs; empty means every path.
142 /// - `options` - only `renames` and `filter` are used.
143 ///
144 /// # Errors
145 ///
146 /// Returns object read, rename detection, and pathspec errors, plus
147 /// [`crate::error::Error::RequiresWorkTree`] for a working-tree target in a bare repository.
148 pub fn entries(
149 &self,
150 target: &DiffTarget,
151 pathspecs: &[String],
152 options: &DiffOptions,
153 ) -> Result<crate::diff::DiffEntriesResult> {
154 match stored_target_entries(self.repo, target, pathspecs, options)? {
155 Some(result) => limit_target_entries(self.repo, result, pathspecs, options),
156 None => self.uncommitted()?.entries(target, pathspecs, options),
157 }
158 }
159
160 /// Structured per-file diffs for `target`, honoring `pathspecs` and line-diff `opts`.
161 ///
162 /// # Errors
163 ///
164 /// Propagates index, ODB, and worktree diff failures.
165 pub fn files(
166 &self,
167 target: &DiffTarget,
168 pathspecs: &[String],
169 opts: &DiffOptions,
170 ) -> Result<Vec<FileDiff>> {
171 collect_files_for_target(self.repo, target, pathspecs, opts.clone())
172 }
173}