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