Skip to content

Commit db23c78

Browse files
committed
feat: add Status iterator.
TBD
1 parent 4b45164 commit db23c78

File tree

5 files changed

+222
-17
lines changed

5 files changed

+222
-17
lines changed

gix/Cargo.toml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ comfort = ["gix-features/progress-unit-bytes", "gix-features/progress-unit-human
6565
command = ["dep:gix-command"]
6666

6767
## Obtain information similar to `git status`.
68-
status = ["gix-status"]
68+
status = ["gix-status", "dirwalk"]
6969

7070
## Utilities for interrupting computations and cleaning up tempfiles.
7171
interrupt = ["dep:signal-hook", "gix-tempfile/signals"]
@@ -131,12 +131,12 @@ blocking-http-transport-curl-rustls = ["blocking-http-transport-curl", "dep:curl
131131
## Stacks with `blocking-network-client` to provide support for HTTP/S using **reqwest**, and implies blocking networking as a whole, making the `https://` transport available.
132132
blocking-http-transport-reqwest = ["blocking-network-client", "gix-transport/http-client-reqwest"]
133133
## Stacks with `blocking-http-transport-reqwest` and enables `https://` via the `rustls` crate.
134-
blocking-http-transport-reqwest-rust-tls = ["blocking-http-transport-reqwest", "reqwest-for-configuration-only/rustls-tls" ]
134+
blocking-http-transport-reqwest-rust-tls = ["blocking-http-transport-reqwest", "reqwest-for-configuration-only/rustls-tls"]
135135
## Stacks with `blocking-http-transport-reqwest` and enables `https://` via the `rustls` crate.
136136
## This also makes use of `trust-dns` to avoid `getaddrinfo`, but note it comes with its own problems.
137137
blocking-http-transport-reqwest-rust-tls-trust-dns = ["blocking-http-transport-reqwest", "reqwest-for-configuration-only/rustls-tls", "reqwest-for-configuration-only/trust-dns"]
138138
## Stacks with `blocking-http-transport-reqwest` and enables `https://` via the `native-tls` crate.
139-
blocking-http-transport-reqwest-native-tls = ["blocking-http-transport-reqwest", "reqwest-for-configuration-only/default-tls" ]
139+
blocking-http-transport-reqwest-native-tls = ["blocking-http-transport-reqwest", "reqwest-for-configuration-only/default-tls"]
140140

141141

142142
#! #### Performance
@@ -186,11 +186,11 @@ pack-cache-lru-dynamic = ["gix-pack/pack-cache-lru-dynamic"]
186186

187187
## Activate other features that maximize performance, like usage of threads, `zlib-ng` and access to caching in object databases.
188188
## Note that some platforms might suffer from compile failures, which is when `max-performance-safe` should be used.
189-
max-performance = [ "max-performance-safe", "zlib-ng", "fast-sha1" ]
189+
max-performance = ["max-performance-safe", "zlib-ng", "fast-sha1"]
190190

191191
## If enabled, use assembly versions of sha1 on supported platforms.
192192
## This might cause compile failures as well which is why it can be turned off separately.
193-
fast-sha1 = [ "gix-features/fast-sha1" ]
193+
fast-sha1 = ["gix-features/fast-sha1"]
194194

195195
## Use the C-based zlib-ng backend, which can compress and decompress significantly faster.
196196
## Note that this will cause duplicate symbol errors if the application also depends on `zlib` - use `zlib-ng-compat` in that case.
@@ -215,7 +215,7 @@ zlib-stock = ["gix-features/zlib-stock"]
215215
verbose-object-parsing-errors = ["gix-object/verbose-object-parsing-errors"]
216216

217217
## Data structures implement `serde::Serialize` and `serde::Deserialize`.
218-
serde = [ "dep:serde",
218+
serde = ["dep:serde",
219219
"gix-pack/serde",
220220
"gix-object/serde",
221221
"gix-protocol?/serde",
@@ -286,7 +286,7 @@ gix-hashtable = { version = "^0.5.1", path = "../gix-hashtable" }
286286
gix-commitgraph = { version = "^0.24.1", path = "../gix-commitgraph" }
287287
gix-pathspec = { version = "^0.7.0", path = "../gix-pathspec", optional = true }
288288
gix-submodule = { version = "^0.9.0", path = "../gix-submodule", optional = true }
289-
gix-status = { version = "^0.6.0", path = "../gix-status", optional = true }
289+
gix-status = { version = "^0.6.0", path = "../gix-status", optional = true, features = ["worktree-rewrites"] }
290290
gix-command = { version = "^0.3.5", path = "../gix-command", optional = true }
291291

292292
gix-worktree-stream = { version = "^0.10.0", path = "../gix-worktree-stream", optional = true }
@@ -301,7 +301,7 @@ prodash = { workspace = true, optional = true, features = ["progress-tree"] }
301301
once_cell = "1.14.0"
302302
signal-hook = { version = "0.3.9", default-features = false, optional = true }
303303
thiserror = "1.0.26"
304-
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]}
304+
serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] }
305305
smallvec = "1.9.0"
306306
async-std = { version = "1.12.0", optional = true }
307307

gix/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,6 @@ pub use gix_ref as refs;
133133
pub use gix_refspec as refspec;
134134
pub use gix_revwalk as revwalk;
135135
pub use gix_sec as sec;
136-
#[cfg(feature = "status")]
137-
pub use gix_status as status;
138136
pub use gix_tempfile as tempfile;
139137
pub use gix_trace as trace;
140138
pub use gix_traverse as traverse;
@@ -317,6 +315,10 @@ pub mod init;
317315
/// Not to be confused with 'status'.
318316
pub mod state;
319317

318+
///
319+
#[cfg(feature = "status")]
320+
pub mod status;
321+
320322
///
321323
pub mod shallow;
322324

gix/src/pathspec.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ impl<'repo> Pathspec<'repo> {
6767
)?,
6868
)?;
6969
let cache = needs_cache.then(make_attributes).transpose()?;
70+
71+
gix_trace::debug!(
72+
longest_prefix = ?search.longest_common_directory(),
73+
prefix_dir = ?search.prefix_directory(),
74+
patterns = ?search.patterns().map(gix_pathspec::Pattern::path).collect::<Vec<_>>()
75+
);
76+
7077
Ok(Self {
7178
repo,
7279
search,

gix/src/repository/dirwalk.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub enum Error {
99
#[error(transparent)]
1010
Walk(#[from] gix_dir::walk::Error),
1111
#[error("A working tree is required to perform a directory walk")]
12-
MissinWorkDir,
12+
MissingWorkDir,
1313
#[error(transparent)]
1414
Excludes(#[from] config::exclude_stack::Error),
1515
#[error(transparent)]
@@ -57,7 +57,7 @@ impl Repository {
5757
delegate: &mut dyn gix_dir::walk::Delegate,
5858
) -> Result<Outcome<'_>, Error> {
5959
let _span = gix_trace::coarse!("gix::dirwalk");
60-
let workdir = self.work_dir().ok_or(Error::MissinWorkDir)?;
60+
let workdir = self.work_dir().ok_or(Error::MissingWorkDir)?;
6161
let mut excludes = self.excludes(
6262
index,
6363
None,
@@ -70,11 +70,6 @@ impl Repository {
7070
index,
7171
crate::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,
7272
)?;
73-
gix_trace::debug!(
74-
longest_prefix = ?pathspec.search.longest_common_directory(),
75-
prefix_dir = ?pathspec.search.prefix_directory(),
76-
patterns = ?pathspec.search.patterns().map(gix_pathspec::Pattern::path).collect::<Vec<_>>()
77-
);
7873

7974
let git_dir_realpath =
8075
crate::path::realpath_opts(self.git_dir(), self.current_dir(), crate::path::realpath::MAX_SYMLINKS)?;

gix/src/status.rs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
use crate::Repository;
2+
pub use gix_status as plumbing;
3+
4+
/// A structure to hold options configuring the status request, which can then be turned into an iterator.
5+
#[derive(Clone)]
6+
pub struct Platform<'repo> {
7+
_repo: &'repo Repository,
8+
}
9+
10+
/// Status
11+
impl Repository {
12+
/// Obtain a platform for configuring and traversing git repository status information.
13+
///
14+
/// By default, this is set to the fastest and most immediate way of obtaining a status,
15+
/// which is most similar to
16+
/// `git status --untracked=all --ignored=no --no-renames --ignore-submodules=all`
17+
///
18+
/// ### Deviation
19+
///
20+
/// Whereas Git runs the index-modified check before the directory walk to set entries
21+
/// as up-to-date to (potentially) safe some disk-access, we run both in parallel which
22+
/// ultimately is much faster.
23+
// TODO: if untracked and ignored entries are disabled, don't run a dirwalk at all.
24+
// TODO: submodule support with reasonable configurability.
25+
pub fn status(&self) -> Platform<'_> {
26+
Platform { _repo: self }
27+
}
28+
}
29+
30+
///
31+
pub mod index_worktree {
32+
use crate::bstr::BStr;
33+
use crate::{config, Repository};
34+
use gix_status::index_as_worktree::traits::{CompareBlobs, SubmoduleStatus};
35+
use std::sync::atomic::AtomicBool;
36+
37+
/// The error returned by [Repository::index_worktree_status()].
38+
#[derive(Debug, thiserror::Error)]
39+
#[allow(missing_docs)]
40+
pub enum Error {
41+
#[error("A working tree is required to perform a directory walk")]
42+
MissingWorkDir,
43+
#[error(transparent)]
44+
AttributesAndExcludes(#[from] crate::repository::attributes::Error),
45+
#[error(transparent)]
46+
Pathspec(#[from] crate::pathspec::init::Error),
47+
#[error(transparent)]
48+
Prefix(#[from] gix_path::realpath::Error),
49+
#[error(transparent)]
50+
FilesystemOptions(#[from] config::boolean::Error),
51+
#[error(transparent)]
52+
IndexAsWorktreeWithRenames(#[from] gix_status::index_as_worktree_with_renames::Error),
53+
#[error(transparent)]
54+
StatOptions(#[from] config::stat_options::Error),
55+
#[error(transparent)]
56+
ResourceCache(#[from] crate::diff::resource_cache::Error),
57+
}
58+
59+
/// Options for use with [Repository::index_worktree_status()].
60+
#[derive(Debug, Clone, Copy, PartialEq)]
61+
pub struct Options {
62+
/// The way all output should be sorted.
63+
///
64+
/// If `None`, and depending on the `rewrites` field, output will be immediate but the output order
65+
/// isn't determined, and may differ between two runs. `rewrites` also depend on the order of entries that
66+
/// are presented to it, hence for deterministic results, sorting needs to be enabled.
67+
///
68+
/// If `Some(_)`, all entries are collected beforehand, so they can be sorted before outputting any of them
69+
/// to the user.
70+
///
71+
/// If immediate output of entries in any order is desired, this should be `None`,
72+
/// along with `rewrites` being `None` as well.
73+
pub sorting: Option<gix_status::index_as_worktree_with_renames::Sorting>,
74+
/// If not `None`, the options to configure the directory walk, determining how its results will look like.
75+
///
76+
/// If `None`, only modification checks are performed.
77+
///
78+
/// Can be instantiated with [Repository::dirwalk_options()].
79+
pub dirwalk_options: Option<crate::dirwalk::Options>,
80+
/// If not `None`, along with configured `dirwalk_options`, rewrite tracking will be performed between the
81+
/// index and the working tree.
82+
/// Note that there is no git-configuration specific to index-worktree rename tracking.
83+
pub rewrites: Option<gix_diff::Rewrites>,
84+
/// If set, don't use more than this amount of threads for the tracked modification check.
85+
/// Otherwise, usually use as many threads as there are logical cores.
86+
/// A value of 0 is interpreted as no-limit
87+
pub thread_limit: Option<usize>,
88+
}
89+
90+
impl Repository {
91+
/// Obtain the status between the index and the worktree, involving modification checks
92+
/// for all tracked files along with information about untracked (and posisbly ignored) files (if configured).
93+
///
94+
/// * `index`
95+
/// - The index to use for modification checks, and to know which files are tacked when applying the dirwalk.
96+
/// * `patterns`
97+
/// - Optional patterns to use to limit the paths to look at. If empty, all paths are considered.
98+
/// * `delegate`
99+
/// - The sink for receiving all status data.
100+
/// * `compare` and `submodule`
101+
/// - Implementations for fine-grained control over what happens if a hash must be recalculated, or submodule
102+
/// information must be retrieved.
103+
/// * `progress`
104+
/// - A progress indication for index modification checks.
105+
/// * `should_interrupt`
106+
/// - A flag to stop the whole operation.
107+
/// * `options`
108+
/// - Additional configuration for all parts of the operation.
109+
///
110+
/// ### Note
111+
///
112+
/// This is a lower-level method, prefer the [`status`](Repository::status()) method for greater ease of use.
113+
#[allow(clippy::too_many_arguments)]
114+
pub fn index_worktree_status<'index, T, U, E>(
115+
&self,
116+
index: &'index gix_index::State,
117+
patterns: impl IntoIterator<Item = impl AsRef<BStr>>,
118+
delegate: &mut impl gix_status::index_as_worktree_with_renames::VisitEntry<
119+
'index,
120+
ContentChange = T,
121+
SubmoduleStatus = U,
122+
>,
123+
compare: impl CompareBlobs<Output = T> + Send + Clone,
124+
submodule: impl SubmoduleStatus<Output = U, Error = E> + Send + Clone,
125+
progress: &mut dyn gix_features::progress::Progress,
126+
should_interrupt: &AtomicBool,
127+
options: Options,
128+
) -> Result<gix_status::index_as_worktree_with_renames::Outcome, Error>
129+
where
130+
T: Send + Clone,
131+
U: Send + Clone,
132+
E: std::error::Error + Send + Sync + 'static,
133+
{
134+
let _span = gix_trace::coarse!("gix::index_worktree_status");
135+
let workdir = self.work_dir().ok_or(Error::MissingWorkDir)?;
136+
let attrs_and_excludes = self.attributes(
137+
index,
138+
crate::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,
139+
crate::worktree::stack::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped,
140+
None,
141+
)?;
142+
let pathspec = crate::Pathspec::new(
143+
self,
144+
options
145+
.dirwalk_options
146+
.as_ref()
147+
.map_or(false, |opts| opts.empty_patterns_match_prefix),
148+
patterns,
149+
true, /* inherit ignore case */
150+
|| Ok(attrs_and_excludes.clone()),
151+
)?;
152+
153+
let cwd = self.current_dir();
154+
let git_dir_realpath =
155+
crate::path::realpath_opts(self.git_dir(), cwd, crate::path::realpath::MAX_SYMLINKS)?;
156+
let fs_caps = self.filesystem_options()?;
157+
let accelerate_lookup = fs_caps.ignore_case.then(|| index.prepare_icase_backing());
158+
let resource_cache = crate::diff::resource_cache(
159+
self,
160+
gix_diff::blob::pipeline::Mode::ToGit,
161+
attrs_and_excludes.inner,
162+
gix_diff::blob::pipeline::WorktreeRoots {
163+
old_root: None,
164+
new_root: Some(workdir.to_owned()),
165+
},
166+
)?;
167+
168+
let out = gix_status::index_as_worktree_with_renames(
169+
index,
170+
workdir,
171+
delegate,
172+
compare,
173+
submodule,
174+
self.objects.clone().into_arc().expect("arc conversion always works"),
175+
progress,
176+
gix_status::index_as_worktree_with_renames::Context {
177+
pathspec: pathspec.search,
178+
resource_cache,
179+
should_interrupt,
180+
dirwalk: gix_status::index_as_worktree_with_renames::DirwalkContext {
181+
git_dir_realpath: git_dir_realpath.as_path(),
182+
current_dir: cwd,
183+
ignore_case_index_lookup: accelerate_lookup.as_ref(),
184+
},
185+
},
186+
gix_status::index_as_worktree_with_renames::Options {
187+
sorting: options.sorting,
188+
object_hash: self.object_hash(),
189+
tracked_file_modifications: gix_status::index_as_worktree::Options {
190+
fs: fs_caps,
191+
thread_limit: options.thread_limit,
192+
stat: self.stat_options()?,
193+
},
194+
dirwalk: options.dirwalk_options.map(Into::into),
195+
rewrites: options.rewrites,
196+
},
197+
)?;
198+
Ok(out)
199+
}
200+
}
201+
}

0 commit comments

Comments
 (0)