Skip to content

Commit 83f3d93

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 83f3d93

File tree

6 files changed

+155
-4
lines changed

6 files changed

+155
-4
lines changed

gix/Cargo.toml

Lines changed: 2 additions & 2 deletions
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

Lines changed: 11 additions & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 3 additions & 0 deletions
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

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

gix/src/worktree/mod.rs

Lines changed: 1 addition & 0 deletions
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)