Skip to content

Commit 3eadb0e

Browse files
committed
feat!: add status::Platform::into_iter() for obtaining a complete status.
Note that it is still possible to disable the head-index status. Types moved around, effectivey removing the `iter::` module for most more general types, i.e. those that are quite genericlally useful in a status.
1 parent a6f397f commit 3eadb0e

File tree

13 files changed

+800
-406
lines changed

13 files changed

+800
-406
lines changed

gitoxide-core/src/repository/status.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use anyhow::bail;
22
use gix::bstr::{BStr, BString, ByteSlice};
3-
use gix::status::index_worktree::iter::Item;
3+
use gix::status::index_worktree::Item;
44
use gix_status::index_as_worktree::{Change, Conflict, EntryStatus};
55
use std::path::Path;
66

gix/src/dirwalk/iter.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,12 @@ impl Iterator for Iter {
160160
#[cfg(feature = "parallel")]
161161
impl Drop for Iter {
162162
fn drop(&mut self) {
163-
crate::util::parallel_iter_drop(self.rx_and_join.take(), &self.should_interrupt);
163+
crate::util::parallel_iter_drop(
164+
self.rx_and_join
165+
.take()
166+
.map(|(rx, handle)| (rx, handle, None::<std::thread::JoinHandle<()>>)),
167+
&self.should_interrupt,
168+
);
164169
}
165170
}
166171

gix/src/status/index_worktree.rs

Lines changed: 144 additions & 381 deletions
Large diffs are not rendered by default.

gix/src/status/iter/mod.rs

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
use crate::bstr::BString;
2+
use crate::config::cache::util::ApplyLeniencyDefault;
3+
use crate::status::index_worktree::BuiltinSubmoduleStatus;
4+
use crate::status::{index_worktree, tree_index, Platform};
5+
use crate::worktree::IndexPersistedOrInMemory;
6+
use gix_status::index_as_worktree::{Change, EntryStatus};
7+
use std::sync::atomic::Ordering;
8+
9+
pub(super) mod types;
10+
use types::{ApplyChange, Item, Iter, Outcome};
11+
12+
/// Lifecycle
13+
impl<Progress> Platform<'_, Progress>
14+
where
15+
Progress: gix_features::progress::Progress,
16+
{
17+
/// Turn the platform into an iterator for changes between the head-tree and the index, and the index and the working tree,
18+
/// while optionally listing untracked and/or ignored files.
19+
///
20+
/// * `patterns`
21+
/// - Optional patterns to use to limit the paths to look at. If empty, all paths are considered.
22+
#[doc(alias = "diff_index_to_workdir", alias = "git2")]
23+
pub fn into_iter(
24+
self,
25+
patterns: impl IntoIterator<Item = BString>,
26+
) -> Result<Iter, crate::status::into_iter::Error> {
27+
let index = match self.index {
28+
None => IndexPersistedOrInMemory::Persisted(self.repo.index_or_empty()?),
29+
Some(index) => index,
30+
};
31+
32+
let obtain_tree_id = || -> Result<Option<gix_hash::ObjectId>, crate::status::into_iter::Error> {
33+
Ok(match self.head_tree {
34+
Some(None) => Some(self.repo.head_tree_id()?.into()),
35+
Some(Some(tree_id)) => Some(tree_id),
36+
None => None,
37+
})
38+
};
39+
40+
let skip_hash = self
41+
.repo
42+
.config
43+
.resolved
44+
.boolean(crate::config::tree::Index::SKIP_HASH)
45+
.map(|res| crate::config::tree::Index::SKIP_HASH.enrich_error(res))
46+
.transpose()
47+
.with_lenient_default(self.repo.config.lenient_config)?
48+
.unwrap_or_default();
49+
let should_interrupt = self.should_interrupt.clone().unwrap_or_default();
50+
let submodule = BuiltinSubmoduleStatus::new(self.repo.clone().into_sync(), self.submodules)?;
51+
#[cfg(feature = "parallel")]
52+
{
53+
let (tx, rx) = std::sync::mpsc::channel();
54+
let patterns: Vec<_> = patterns.into_iter().collect();
55+
let join_tree_index = if let Some(tree_id) = obtain_tree_id()? {
56+
std::thread::Builder::new()
57+
.name("gix::status::tree_index::producer".into())
58+
.spawn({
59+
let repo = self.repo.clone().into_sync();
60+
let should_interrupt = should_interrupt.clone();
61+
let tx = tx.clone();
62+
let tree_index_renames = self.tree_index_renames;
63+
let index = index.clone();
64+
let crate::Pathspec { repo: _, stack, search } = self
65+
.repo
66+
.index_worktree_status_pathspec::<crate::status::into_iter::Error>(
67+
&patterns,
68+
&index,
69+
self.index_worktree_options.dirwalk_options.as_ref(),
70+
)?;
71+
move || -> Result<_, _> {
72+
let repo = repo.to_thread_local();
73+
let mut pathspec = crate::Pathspec {
74+
repo: &repo,
75+
stack,
76+
search,
77+
};
78+
repo.tree_index_status(
79+
&tree_id,
80+
&index,
81+
Some(&mut pathspec),
82+
tree_index_renames,
83+
|change, _, _| {
84+
let action = if tx.send(change.into_owned().into()).is_err()
85+
|| should_interrupt.load(Ordering::Acquire)
86+
{
87+
gix_diff::index::Action::Cancel
88+
} else {
89+
gix_diff::index::Action::Continue
90+
};
91+
Ok::<_, std::convert::Infallible>(action)
92+
},
93+
)
94+
}
95+
})
96+
.map_err(crate::status::into_iter::Error::SpawnThread)?
97+
.into()
98+
} else {
99+
None
100+
};
101+
let mut collect = Collect { tx };
102+
let join_index_worktree = std::thread::Builder::new()
103+
.name("gix::status::index_worktree::producer".into())
104+
.spawn({
105+
let repo = self.repo.clone().into_sync();
106+
let options = self.index_worktree_options;
107+
let should_interrupt = should_interrupt.clone();
108+
let mut progress = self.progress;
109+
move || -> Result<_, index_worktree::Error> {
110+
let repo = repo.to_thread_local();
111+
let out = repo.index_worktree_status(
112+
&index,
113+
patterns,
114+
&mut collect,
115+
gix_status::index_as_worktree::traits::FastEq,
116+
submodule,
117+
&mut progress,
118+
&should_interrupt,
119+
options,
120+
)?;
121+
Ok(Outcome {
122+
index_worktree: out,
123+
tree_index: None,
124+
worktree_index: index,
125+
changes: None,
126+
skip_hash,
127+
})
128+
}
129+
})
130+
.map_err(crate::status::into_iter::Error::SpawnThread)?;
131+
132+
Ok(Iter {
133+
rx_and_join: Some((rx, join_index_worktree, join_tree_index)),
134+
should_interrupt,
135+
index_changes: Vec::new(),
136+
out: None,
137+
})
138+
}
139+
#[cfg(not(feature = "parallel"))]
140+
{
141+
let mut collect = Collect { items: Vec::new() };
142+
143+
let repo = self.repo;
144+
let options = self.index_worktree_options;
145+
let mut progress = self.progress;
146+
let patterns: Vec<BString> = patterns.into_iter().collect();
147+
let (mut items, tree_index) = match obtain_tree_id()? {
148+
Some(tree_id) => {
149+
let mut pathspec = repo.index_worktree_status_pathspec::<crate::status::into_iter::Error>(
150+
&patterns,
151+
&index,
152+
self.index_worktree_options.dirwalk_options.as_ref(),
153+
)?;
154+
let mut items = Vec::new();
155+
let tree_index = self.repo.tree_index_status(
156+
&tree_id,
157+
&index,
158+
Some(&mut pathspec),
159+
self.tree_index_renames,
160+
|change, _, _| {
161+
items.push(change.into_owned().into());
162+
let action = if should_interrupt.load(Ordering::Acquire) {
163+
gix_diff::index::Action::Cancel
164+
} else {
165+
gix_diff::index::Action::Continue
166+
};
167+
Ok::<_, std::convert::Infallible>(action)
168+
},
169+
)?;
170+
(items, Some(tree_index))
171+
}
172+
None => (Vec::new(), None),
173+
};
174+
let out = repo.index_worktree_status(
175+
&index,
176+
patterns,
177+
&mut collect,
178+
gix_status::index_as_worktree::traits::FastEq,
179+
submodule,
180+
&mut progress,
181+
&should_interrupt,
182+
options,
183+
)?;
184+
let mut iter = Iter {
185+
items: Vec::new().into_iter(),
186+
index_changes: Vec::new(),
187+
out: None,
188+
};
189+
let mut out = Outcome {
190+
index_worktree: out,
191+
worktree_index: index,
192+
tree_index,
193+
changes: None,
194+
skip_hash,
195+
};
196+
items.extend(
197+
collect
198+
.items
199+
.into_iter()
200+
.filter_map(|item| iter.maybe_keep_index_change(item)),
201+
);
202+
out.changes = (!iter.index_changes.is_empty()).then(|| std::mem::take(&mut iter.index_changes));
203+
iter.items = items.into_iter();
204+
iter.out = Some(out);
205+
Ok(iter)
206+
}
207+
}
208+
}
209+
210+
/// The error returned for each item returned by [`Iter`].
211+
#[derive(Debug, thiserror::Error)]
212+
#[allow(missing_docs)]
213+
pub enum Error {
214+
#[error(transparent)]
215+
IndexWorktree(#[from] index_worktree::Error),
216+
#[error(transparent)]
217+
TreeIndex(#[from] tree_index::Error),
218+
}
219+
220+
impl Iterator for Iter {
221+
type Item = Result<Item, Error>;
222+
223+
fn next(&mut self) -> Option<Self::Item> {
224+
#[cfg(feature = "parallel")]
225+
loop {
226+
let (rx, _join_worktree, _join_tree) = self.rx_and_join.as_ref()?;
227+
match rx.recv().ok() {
228+
Some(item) => {
229+
if let Some(item) = self.maybe_keep_index_change(item) {
230+
break Some(Ok(item));
231+
}
232+
continue;
233+
}
234+
None => {
235+
let (_rx, worktree_handle, tree_handle) = self.rx_and_join.take()?;
236+
let tree_index = if let Some(handle) = tree_handle {
237+
match handle.join().expect("no panic") {
238+
Ok(out) => Some(out),
239+
Err(err) => break Some(Err(err.into())),
240+
}
241+
} else {
242+
None
243+
};
244+
break match worktree_handle.join().expect("no panic") {
245+
Ok(mut out) => {
246+
out.changes = Some(std::mem::take(&mut self.index_changes));
247+
out.tree_index = tree_index;
248+
self.out = Some(out);
249+
None
250+
}
251+
Err(err) => Some(Err(err.into())),
252+
};
253+
}
254+
}
255+
}
256+
#[cfg(not(feature = "parallel"))]
257+
self.items.next().map(Ok)
258+
}
259+
}
260+
261+
/// Access
262+
impl Iter {
263+
/// Return the outcome of the iteration, or `None` if the iterator isn't fully consumed.
264+
pub fn outcome_mut(&mut self) -> Option<&mut Outcome> {
265+
self.out.as_mut()
266+
}
267+
268+
/// Turn the iterator into the iteration outcome, which is `None` on error or if the iteration
269+
/// isn't complete.
270+
pub fn into_outcome(mut self) -> Option<Outcome> {
271+
self.out.take()
272+
}
273+
}
274+
275+
impl Iter {
276+
fn maybe_keep_index_change(&mut self, item: Item) -> Option<Item> {
277+
let change = match item {
278+
Item::IndexWorktree(index_worktree::Item::Modification {
279+
status: EntryStatus::NeedsUpdate(stat),
280+
entry_index,
281+
..
282+
}) => (entry_index, ApplyChange::NewStat(stat)),
283+
Item::IndexWorktree(index_worktree::Item::Modification {
284+
status:
285+
EntryStatus::Change(Change::Modification {
286+
set_entry_stat_size_zero,
287+
..
288+
}),
289+
entry_index,
290+
..
291+
}) if set_entry_stat_size_zero => (entry_index, ApplyChange::SetSizeToZero),
292+
_ => return Some(item),
293+
};
294+
295+
self.index_changes.push(change);
296+
None
297+
}
298+
}
299+
300+
#[cfg(feature = "parallel")]
301+
impl Drop for Iter {
302+
fn drop(&mut self) {
303+
crate::util::parallel_iter_drop(self.rx_and_join.take(), &self.should_interrupt);
304+
}
305+
}
306+
307+
struct Collect {
308+
#[cfg(feature = "parallel")]
309+
tx: std::sync::mpsc::Sender<Item>,
310+
#[cfg(not(feature = "parallel"))]
311+
items: Vec<Item>,
312+
}
313+
314+
impl<'index> gix_status::index_as_worktree_with_renames::VisitEntry<'index> for Collect {
315+
type ContentChange =
316+
<gix_status::index_as_worktree::traits::FastEq as gix_status::index_as_worktree::traits::CompareBlobs>::Output;
317+
type SubmoduleStatus = <BuiltinSubmoduleStatus as gix_status::index_as_worktree::traits::SubmoduleStatus>::Output;
318+
319+
fn visit_entry(
320+
&mut self,
321+
entry: gix_status::index_as_worktree_with_renames::Entry<'index, Self::ContentChange, Self::SubmoduleStatus>,
322+
) {
323+
// NOTE: we assume that the receiver triggers interruption so the operation will stop if the receiver is down.
324+
let item = Item::IndexWorktree(entry.into());
325+
#[cfg(feature = "parallel")]
326+
self.tx.send(item).ok();
327+
#[cfg(not(feature = "parallel"))]
328+
self.items.push(item);
329+
}
330+
}

0 commit comments

Comments
 (0)