Skip to content

Commit f0e23b4

Browse files
committed
feat: Repository::tree_index_status() to see the changes between a tree and an index.
It also respects `status.rename` and `status.renameLimit` to configure rename tracking.
1 parent c4e8745 commit f0e23b4

File tree

6 files changed

+142
-4
lines changed

6 files changed

+142
-4
lines changed

gix/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ comfort = [
8686
command = ["dep:gix-command"]
8787

8888
## Obtain information similar to `git status`.
89-
status = ["gix-status", "dirwalk", "index", "blob-diff"]
89+
status = ["gix-status", "dirwalk", "index", "blob-diff", "gix-diff/index"]
9090

9191
## Utilities for interrupting computations and cleaning up tempfiles.
9292
interrupt = ["dep:signal-hook", "gix-tempfile/signals", "dep:parking_lot"]
@@ -374,7 +374,7 @@ gix-command = { version = "^0.4.0", path = "../gix-command", optional = true }
374374

375375
gix-worktree-stream = { version = "^0.18.0", path = "../gix-worktree-stream", optional = true }
376376
gix-archive = { version = "^0.18.0", path = "../gix-archive", default-features = false, optional = true }
377-
gix-blame = { version= "^0.0.0", path ="../gix-blame", optional = true }
377+
gix-blame = { version = "^0.0.0", path = "../gix-blame", optional = true }
378378

379379
# For communication with remotes
380380
gix-protocol = { version = "^0.47.0", path = "../gix-protocol" }

gix/src/config/tree/sections/status.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ impl Status {
99
&config::Tree::STATUS,
1010
validate::ShowUntrackedFiles,
1111
);
12+
/// The `status.renameLimit` key.
13+
pub const RENAME_LIMIT: keys::UnsignedInteger = keys::UnsignedInteger::new_unsigned_integer(
14+
"renameLimit",
15+
&config::Tree::MERGE,
16+
)
17+
.with_note(
18+
"The limit is actually squared, so 1000 stands for up to 1 million diffs if fuzzy rename tracking is enabled",
19+
);
20+
/// The `status.renames` key.
21+
pub const RENAMES: super::diff::Renames = super::diff::Renames::new_renames("renames", &config::Tree::MERGE);
1222
}
1323

1424
/// The `status.showUntrackedFiles` key.
@@ -41,7 +51,7 @@ impl Section for Status {
4151
}
4252

4353
fn keys(&self) -> &[&dyn Key] {
44-
&[&Self::SHOW_UNTRACKED_FILES]
54+
&[&Self::SHOW_UNTRACKED_FILES, &Self::RENAMES, &Self::RENAME_LIMIT]
4555
}
4656
}
4757

gix/src/pathspec.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ impl PathspecDetached {
202202
}
203203
}
204204

205-
fn is_dir_to_mode(is_dir: bool) -> gix_index::entry::Mode {
205+
pub(crate) fn is_dir_to_mode(is_dir: bool) -> gix_index::entry::Mode {
206206
if is_dir {
207207
gix_index::entry::Mode::DIR
208208
} else {

gix/src/status/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,6 @@ mod platform;
176176

177177
///
178178
pub mod index_worktree;
179+
180+
///
181+
pub mod tree_index;

gix/src/status/tree_index.rs

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use crate::config::tree;
2+
use crate::Repository;
3+
4+
/// The error returned by [Repository::tree_index_status()].
5+
#[derive(Debug, thiserror::Error)]
6+
#[allow(missing_docs)]
7+
pub enum Error {
8+
#[error(transparent)]
9+
OpenWorktreeIndex(#[from] crate::worktree::open_index::Error),
10+
#[error(transparent)]
11+
IndexFromMTree(#[from] crate::repository::index_from_tree::Error),
12+
#[error(transparent)]
13+
RewritesConfiguration(#[from] crate::diff::new_rewrites::Error),
14+
#[error("Could not create diff-cache for similarity checks")]
15+
DiffResourceCache(#[from] crate::repository::diff_resource_cache::Error),
16+
#[error(transparent)]
17+
TreeIndexDiff(#[from] gix_diff::index::Error),
18+
}
19+
20+
/// The outcome of [Repository::tree_index_status()].
21+
#[derive(Clone)]
22+
pub struct Outcome {
23+
/// Additional information produced by the rename tracker.
24+
///
25+
/// It may be `None` if rename tracking was disabled.
26+
pub rewrite: Option<gix_diff::rewrites::Outcome>,
27+
/// The index produced from the input `tree` for the purpose of diffing.
28+
///
29+
/// At some point this might go away once it's possible to diff an index from a tree directly.
30+
pub tree_index: gix_index::State,
31+
/// A reference to the index at `.git/index`.
32+
pub worktree_index: crate::worktree::Index,
33+
}
34+
35+
impl Repository {
36+
/// Produce the `git status` portion that shows the difference between `tree_id` (usually `HEAD^{tree}`) and the current `.git/index`,
37+
/// and pass all changes to `cb(change, tree_index, worktree_index)` with full access to both indices that contributed to the change.
38+
/// *(It's notable that internally, the `tree_id` is converted into an index before diffing these)*.
39+
/// Set `pathspec` to `Some(_)` to further reduce the set of files to check.
40+
///
41+
/// ### Notes
42+
///
43+
/// * This method will fail if there is no `.git/index` file.
44+
/// * This is a low-level method - prefer the [`Repository::status()`] platform instead for access to various iterators
45+
/// over the same information.
46+
pub fn tree_index_status<'repo, E>(
47+
&'repo self,
48+
tree_id: &gix_hash::oid,
49+
pathspec: Option<&mut crate::Pathspec<'repo>>,
50+
mut cb: impl FnMut(
51+
gix_diff::index::ChangeRef<'_, '_>,
52+
&gix_index::State,
53+
&gix_index::State,
54+
) -> Result<gix_diff::index::Action, E>,
55+
) -> Result<Outcome, Error>
56+
where
57+
E: Into<Box<dyn std::error::Error + Send + Sync>>,
58+
{
59+
let worktree_index = self.index()?;
60+
let tree_index: gix_index::State = self.index_from_tree(tree_id)?.into();
61+
let (mut rewrites, mut is_configured) = crate::diff::utils::new_rewrites_inner(
62+
&self.config.resolved,
63+
self.config.lenient_config,
64+
&tree::Status::RENAMES,
65+
&tree::Status::RENAME_LIMIT,
66+
)?;
67+
if !is_configured {
68+
(rewrites, is_configured) =
69+
crate::diff::utils::new_rewrites(&self.config.resolved, self.config.lenient_config)?;
70+
}
71+
if !is_configured {
72+
rewrites = Some(Default::default());
73+
}
74+
let mut resource_cache = None;
75+
if rewrites.is_some() {
76+
resource_cache = Some(self.diff_resource_cache_for_tree_diff()?);
77+
}
78+
let mut pathspec_storage = None;
79+
if pathspec.is_none() {
80+
pathspec_storage = self
81+
.pathspec(
82+
true,
83+
None::<&str>,
84+
false,
85+
&gix_index::State::new(self.object_hash()),
86+
gix_worktree::stack::state::attributes::Source::IdMapping,
87+
)
88+
.expect("Impossible for this to fail without patterns")
89+
.into();
90+
}
91+
92+
let pathspec = pathspec.unwrap_or(pathspec_storage.as_mut().expect("set if pathspec isn't set by user"));
93+
let rewrite = gix_diff::index(
94+
&tree_index,
95+
&worktree_index,
96+
|change| cb(change, &tree_index, &worktree_index),
97+
rewrites
98+
.zip(resource_cache.as_mut())
99+
.map(|(rewrites, resource_cache)| gix_diff::index::RewriteOptions {
100+
resource_cache,
101+
find: self,
102+
rewrites,
103+
}),
104+
&mut pathspec.search,
105+
&mut |relative_path, case, is_dir, out| {
106+
let stack = pathspec.stack.as_mut().expect("initialized in advance");
107+
stack
108+
.set_case(case)
109+
.at_entry(
110+
relative_path,
111+
Some(crate::pathspec::is_dir_to_mode(is_dir)),
112+
&pathspec.repo.objects,
113+
)
114+
.map_or(false, |platform| platform.matching_attributes(out))
115+
},
116+
)?;
117+
118+
Ok(Outcome {
119+
rewrite,
120+
tree_index,
121+
worktree_index,
122+
})
123+
}
124+
}

gix/src/worktree/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub type Index = gix_fs::SharedFileSnapshot<gix_index::File>;
2323
/// A type to represent an index which either was loaded from disk as it was persisted there, or created on the fly in memory.
2424
#[cfg(feature = "index")]
2525
#[allow(clippy::large_enum_variant)]
26+
#[derive(Clone)]
2627
pub enum IndexPersistedOrInMemory {
2728
/// The index as loaded from disk, and shared across clones of the owning `Repository`.
2829
Persisted(Index),

0 commit comments

Comments
 (0)