gix/status/index_worktree.rs
1use std::sync::atomic::AtomicBool;
2
3use gix_status::index_as_worktree::traits::{CompareBlobs, SubmoduleStatus};
4
5use crate::{
6 bstr::{BStr, BString},
7 config, Repository,
8};
9
10/// The error returned by [Repository::index_worktree_status()].
11#[derive(Debug, thiserror::Error)]
12#[allow(missing_docs)]
13pub enum Error {
14 #[error("A working tree is required to perform a directory walk")]
15 MissingWorkDir,
16 #[error(transparent)]
17 AttributesAndExcludes(#[from] crate::repository::attributes::Error),
18 #[error(transparent)]
19 Pathspec(#[from] crate::pathspec::init::Error),
20 #[error(transparent)]
21 Prefix(#[from] gix_path::realpath::Error),
22 #[error(transparent)]
23 FilesystemOptions(#[from] config::boolean::Error),
24 #[error(transparent)]
25 IndexAsWorktreeWithRenames(#[from] gix_status::index_as_worktree_with_renames::Error),
26 #[error(transparent)]
27 StatOptions(#[from] config::stat_options::Error),
28 #[error(transparent)]
29 ResourceCache(#[from] crate::diff::resource_cache::Error),
30}
31
32/// Options for use with [Repository::index_worktree_status()].
33#[derive(Default, Debug, Clone, Copy, PartialEq)]
34pub struct Options {
35 /// The way all output should be sorted.
36 ///
37 /// If `None`, and depending on the `rewrites` field, output will be immediate but the output order
38 /// isn't determined, and may differ between two runs. `rewrites` also depend on the order of entries that
39 /// are presented to it, hence for deterministic results, sorting needs to be enabled.
40 ///
41 /// If `Some(_)`, all entries are collected beforehand, so they can be sorted before outputting any of them
42 /// to the user.
43 ///
44 /// If immediate output of entries in any order is desired, this should be `None`,
45 /// along with `rewrites` being `None` as well.
46 pub sorting: Option<gix_status::index_as_worktree_with_renames::Sorting>,
47 /// If not `None`, the options to configure the directory walk, determining how its results will look like.
48 ///
49 /// If `None`, only modification checks are performed.
50 ///
51 /// Can be instantiated with [Repository::dirwalk_options()].
52 pub dirwalk_options: Option<crate::dirwalk::Options>,
53 /// If `Some(_)`, along with `Some(_)` in `dirwalk_options`, rewrite tracking will be performed between the
54 /// index and the working tree.
55 /// Note that there is no git-configuration specific to index-worktree rename tracking.
56 /// When rewrite tracking is enabled, there will be a delay for some entries as they partake in the rename-analysis.
57 pub rewrites: Option<gix_diff::Rewrites>,
58 /// If set, don't use more than this amount of threads for the tracked modification check.
59 /// Otherwise, usually use as many threads as there are logical cores.
60 /// A value of 0 is interpreted as no-limit
61 pub thread_limit: Option<usize>,
62}
63
64impl Repository {
65 /// Obtain the status between the index and the worktree, involving modification checks
66 /// for all tracked files along with information about untracked (and posisbly ignored) files (if configured).
67 ///
68 /// * `index`
69 /// - The index to use for modification checks, and to know which files are tacked when applying the dirwalk.
70 /// * `patterns`
71 /// - Optional patterns to use to limit the paths to look at. If empty, all paths are considered.
72 /// * `delegate`
73 /// - The sink for receiving all status data.
74 /// * `compare`
75 /// - The implementations for fine-grained control over what happens if a hash must be recalculated.
76 /// * `submodule`
77 /// - Control what kind of information to retrieve when a submodule is encountered while traversing the index.
78 /// * `progress`
79 /// - A progress indication for index modification checks.
80 /// * `should_interrupt`
81 /// - A flag to stop the whole operation.
82 /// * `options`
83 /// - Additional configuration for all parts of the operation.
84 ///
85 /// ### Note
86 ///
87 /// This is a lower-level method, prefer the [`status`](Repository::status()) method for greater ease of use.
88 #[allow(clippy::too_many_arguments)]
89 pub fn index_worktree_status<'index, T, U, E>(
90 &self,
91 index: &'index gix_index::State,
92 patterns: impl IntoIterator<Item = impl AsRef<BStr>>,
93 delegate: &mut impl gix_status::index_as_worktree_with_renames::VisitEntry<
94 'index,
95 ContentChange = T,
96 SubmoduleStatus = U,
97 >,
98 compare: impl CompareBlobs<Output = T> + Send + Clone,
99 submodule: impl SubmoduleStatus<Output = U, Error = E> + Send + Clone,
100 progress: &mut dyn gix_features::progress::Progress,
101 should_interrupt: &AtomicBool,
102 options: Options,
103 ) -> Result<gix_status::index_as_worktree_with_renames::Outcome, Error>
104 where
105 T: Send + Clone,
106 U: Send + Clone,
107 E: std::error::Error + Send + Sync + 'static,
108 {
109 let _span = gix_trace::coarse!("gix::index_worktree_status");
110 let workdir = self.workdir().ok_or(Error::MissingWorkDir)?;
111 let attrs_and_excludes = self.attributes(
112 index,
113 crate::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,
114 crate::worktree::stack::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped,
115 None,
116 )?;
117 let pathspec =
118 self.index_worktree_status_pathspec::<Error>(patterns, index, options.dirwalk_options.as_ref())?;
119
120 let cwd = self.current_dir();
121 let git_dir_realpath = crate::path::realpath_opts(self.git_dir(), cwd, crate::path::realpath::MAX_SYMLINKS)?;
122 let fs_caps = self.filesystem_options()?;
123 let accelerate_lookup = fs_caps.ignore_case.then(|| index.prepare_icase_backing());
124 let resource_cache = crate::diff::resource_cache(
125 self,
126 gix_diff::blob::pipeline::Mode::ToGit,
127 attrs_and_excludes.inner,
128 gix_diff::blob::pipeline::WorktreeRoots {
129 old_root: None,
130 new_root: Some(workdir.to_owned()),
131 },
132 )?;
133
134 let out = gix_status::index_as_worktree_with_renames(
135 index,
136 workdir,
137 delegate,
138 compare,
139 submodule,
140 self.objects.clone().into_arc().expect("arc conversion always works"),
141 progress,
142 gix_status::index_as_worktree_with_renames::Context {
143 pathspec: pathspec.search,
144 resource_cache,
145 should_interrupt,
146 dirwalk: gix_status::index_as_worktree_with_renames::DirwalkContext {
147 git_dir_realpath: git_dir_realpath.as_path(),
148 current_dir: cwd,
149 ignore_case_index_lookup: accelerate_lookup.as_ref(),
150 },
151 },
152 gix_status::index_as_worktree_with_renames::Options {
153 sorting: options.sorting,
154 object_hash: self.object_hash(),
155 tracked_file_modifications: gix_status::index_as_worktree::Options {
156 fs: fs_caps,
157 thread_limit: options.thread_limit,
158 stat: self.stat_options()?,
159 },
160 dirwalk: options.dirwalk_options.map(Into::into),
161 rewrites: options.rewrites,
162 },
163 )?;
164 Ok(out)
165 }
166
167 pub(super) fn index_worktree_status_pathspec<E>(
168 &self,
169 patterns: impl IntoIterator<Item = impl AsRef<BStr>>,
170 index: &gix_index::State,
171 options: Option<&crate::dirwalk::Options>,
172 ) -> Result<crate::Pathspec<'_>, E>
173 where
174 E: From<crate::repository::attributes::Error> + From<crate::pathspec::init::Error>,
175 {
176 let empty_patterns_match_prefix = options.is_some_and(|opts| opts.empty_patterns_match_prefix);
177 let attrs_and_excludes = self.attributes(
178 index,
179 crate::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,
180 crate::worktree::stack::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped,
181 None,
182 )?;
183 Ok(crate::Pathspec::new(
184 self,
185 empty_patterns_match_prefix,
186 patterns,
187 true, /* inherit ignore case */
188 move || Ok(attrs_and_excludes.inner),
189 )?)
190 }
191}
192
193/// An implementation of a trait to use with [`Repository::index_worktree_status()`] to compute the submodule status
194/// using [Submodule::status()](crate::Submodule::status()).
195#[derive(Clone)]
196pub struct BuiltinSubmoduleStatus {
197 mode: crate::status::Submodule,
198 #[cfg(feature = "parallel")]
199 repo: crate::ThreadSafeRepository,
200 #[cfg(not(feature = "parallel"))]
201 git_dir: std::path::PathBuf,
202 submodule_paths: Vec<BString>,
203}
204
205///
206mod submodule_status {
207 use std::borrow::Cow;
208
209 use crate::config::cache::util::ApplyLeniency;
210 use crate::{
211 bstr,
212 bstr::BStr,
213 config,
214 status::{index_worktree::BuiltinSubmoduleStatus, Submodule},
215 };
216
217 impl BuiltinSubmoduleStatus {
218 /// Create a new instance from a `repo` and a `mode` to control how the submodule status will be obtained.
219 pub fn new(
220 repo: crate::ThreadSafeRepository,
221 mode: Submodule,
222 ) -> Result<Self, crate::submodule::modules::Error> {
223 let local_repo = repo.to_thread_local();
224 let submodule_paths = match local_repo.submodules() {
225 Ok(Some(sm)) => {
226 let mut v: Vec<_> = sm.filter_map(|sm| sm.path().ok().map(Cow::into_owned)).collect();
227 v.sort();
228 v
229 }
230 Ok(None) => Vec::new(),
231 Err(err) => return Err(err),
232 };
233 Ok(Self {
234 mode,
235 #[cfg(feature = "parallel")]
236 repo,
237 #[cfg(not(feature = "parallel"))]
238 git_dir: local_repo.git_dir().to_owned(),
239 submodule_paths,
240 })
241 }
242 }
243
244 /// The error returned submodule status checks.
245 #[derive(Debug, thiserror::Error)]
246 #[allow(missing_docs)]
247 pub enum Error {
248 #[error(transparent)]
249 SubmoduleStatus(#[from] crate::submodule::status::Error),
250 #[error(transparent)]
251 IgnoreConfig(#[from] crate::submodule::config::Error),
252 #[error(transparent)]
253 DiffSubmoduleIgnoreConfig(#[from] config::key::GenericErrorWithValue),
254 }
255
256 impl gix_status::index_as_worktree::traits::SubmoduleStatus for BuiltinSubmoduleStatus {
257 type Output = crate::submodule::Status;
258 type Error = Error;
259
260 fn status(&mut self, _entry: &gix_index::Entry, rela_path: &BStr) -> Result<Option<Self::Output>, Self::Error> {
261 use bstr::ByteSlice;
262 if self
263 .submodule_paths
264 .binary_search_by(|path| path.as_bstr().cmp(rela_path))
265 .is_err()
266 {
267 return Ok(None);
268 }
269 #[cfg(feature = "parallel")]
270 let repo = self.repo.to_thread_local();
271 #[cfg(not(feature = "parallel"))]
272 let Ok(repo) = crate::open(&self.git_dir) else {
273 return Ok(None);
274 };
275 let Ok(Some(mut submodules)) = repo.submodules() else {
276 return Ok(None);
277 };
278 let Some(sm) = submodules.find(|sm| sm.path().is_ok_and(|path| path == rela_path)) else {
279 return Ok(None);
280 };
281 let (ignore, check_dirty) = match self.mode {
282 Submodule::AsConfigured { check_dirty } => {
283 // diff.ignoreSubmodules is the global setting, and if it exists, it overrides the submodule's own ignore setting.
284 let global_ignore = repo
285 .config_snapshot()
286 .string(&config::tree::Diff::IGNORE_SUBMODULES)
287 .map(|value| config::tree::Diff::IGNORE_SUBMODULES.try_into_ignore(value))
288 .transpose()
289 .with_leniency(repo.config.lenient_config)?;
290 if let Some(ignore) = global_ignore {
291 (ignore, check_dirty)
292 } else {
293 // If no global ignore is set, use the submodule's ignore setting.
294 let ignore = sm.ignore()?.unwrap_or_default();
295 (ignore, check_dirty)
296 }
297 }
298 Submodule::Given { ignore, check_dirty } => (ignore, check_dirty),
299 };
300 let status = sm.status(ignore, check_dirty)?;
301 Ok(status.is_dirty().and_then(|dirty| dirty.then_some(status)))
302 }
303 }
304}
305
306/// An iterator for changes between the index and the worktree.
307///
308/// Note that depending on the underlying configuration, there might be a significant delay until the first
309/// item is received due to the buffering necessary to perform rename tracking and/or sorting.
310///
311/// ### Submodules
312///
313/// Note that submodules can be set to 'inactive', which will not exclude them from the status operation, similar to
314/// how `git status` includes them.
315///
316/// ### Index Changes
317///
318/// Changes to the index are collected and it's possible to write the index back using [Outcome::write_changes()](crate::status::Outcome).
319/// Note that these changes are not observable, they will always be kept.
320///
321/// ### Parallel Operation
322///
323/// Note that without the `parallel` feature, the iterator becomes 'serial', which means all status will be computed in advance
324/// and it's non-interruptible, yielding worse performance for is-dirty checks for instance as interruptions won't happen.
325/// It's a crutch that is just there to make single-threaded applications possible at all, as it's not really an iterator
326/// anymore. If this matters, better run [Repository::index_worktree_status()] by hand as it provides all control one would need,
327/// just not as an iterator.
328///
329/// Also, even with `parallel` set, the first call to `next()` will block until there is an item available, without a chance
330/// to interrupt unless [`status::Platform::should_interrupt_*()`](crate::status::Platform::should_interrupt_shared()) was
331/// configured.
332pub struct Iter {
333 inner: crate::status::Iter,
334}
335
336/// The item produced by the iterator
337#[derive(Clone, PartialEq, Debug)]
338pub enum Item {
339 /// A tracked file was modified, and index-specific information is passed.
340 Modification {
341 /// The entry with modifications.
342 entry: gix_index::Entry,
343 /// The index of the `entry` for lookup in [`gix_index::State::entries()`] - useful to look at neighbors.
344 entry_index: usize,
345 /// The repository-relative path of the entry.
346 rela_path: BString,
347 /// The computed status of the entry.
348 status: gix_status::index_as_worktree::EntryStatus<(), crate::submodule::Status>,
349 },
350 /// An entry returned by the directory walk, without any relation to the index.
351 ///
352 /// This can happen if ignored files are returned as well, or if rename-tracking is disabled.
353 DirectoryContents {
354 /// The entry found during the disk traversal.
355 entry: gix_dir::Entry,
356 /// `collapsed_directory_status` is `Some(dir_status)` if this `entry` was part of a directory with the given
357 /// `dir_status` that wasn't the same as the one of `entry` and if [gix_dir::walk::Options::emit_collapsed] was
358 /// [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch).
359 /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All).
360 collapsed_directory_status: Option<gix_dir::entry::Status>,
361 },
362 /// The rewrite tracking discovered a match between a deleted and added file, and considers them equal enough,
363 /// depending on the tracker settings.
364 ///
365 /// Note that the source of the rewrite is always the index as it detects the absence of entries, something that
366 /// can't be done during a directory walk.
367 Rewrite {
368 /// The source of the rewrite operation.
369 source: RewriteSource,
370 /// The untracked entry found during the disk traversal, the destination of the rewrite.
371 ///
372 /// Note that its [`rela_path`](gix_dir::EntryRef::rela_path) is the destination of the rewrite, and the current
373 /// location of the entry.
374 dirwalk_entry: gix_dir::Entry,
375 /// `collapsed_directory_status` is `Some(dir_status)` if this `dirwalk_entry` was part of a directory with the given
376 /// `dir_status` that wasn't the same as the one of `dirwalk_entry` and if [gix_dir::walk::Options::emit_collapsed] was
377 /// [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch).
378 /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All).
379 dirwalk_entry_collapsed_directory_status: Option<gix_dir::entry::Status>,
380 /// The object id as it would appear if the entry was written to the object database, specifically hashed in order to determine equality.
381 /// Note that it doesn't (necessarily) exist in the object database, and may be [null](gix_hash::ObjectId::null) if no hashing
382 /// was performed.
383 dirwalk_entry_id: gix_hash::ObjectId,
384 /// It's `None` if the 'source.id' is equal to `dirwalk_entry_id`, as identity made an actual diff computation unnecessary.
385 /// Otherwise, and if enabled, it's `Some(stats)` to indicate how similar both entries were.
386 diff: Option<gix_diff::blob::DiffLineStats>,
387 /// If true, this rewrite is created by copy, and 'source.id' is pointing to its source.
388 /// Otherwise, it's a rename, and 'source.id' points to a deleted object,
389 /// as renames are tracked as deletions and additions of the same or similar content.
390 copy: bool,
391 },
392}
393
394/// Either an index entry for renames or another directory entry in case of copies.
395#[derive(Clone, PartialEq, Debug)]
396pub enum RewriteSource {
397 /// The source originates in the index and is detected as missing in the working tree.
398 /// This can also happen for copies.
399 RewriteFromIndex {
400 /// The entry that is the source of the rewrite, which means it was removed on disk,
401 /// equivalent to [Change::Removed](gix_status::index_as_worktree::Change::Removed).
402 ///
403 /// Note that the [entry-id](gix_index::Entry::id) is the content-id of the source of the rewrite.
404 source_entry: gix_index::Entry,
405 /// The index of the `source_entry` for lookup in [`gix_index::State::entries()`] - useful to look at neighbors.
406 source_entry_index: usize,
407 /// The repository-relative path of the `source_entry`.
408 source_rela_path: BString,
409 /// The computed status of the `source_entry`.
410 source_status: gix_status::index_as_worktree::EntryStatus<(), crate::submodule::Status>,
411 },
412 /// This source originates in the directory tree and is always the source of copies.
413 CopyFromDirectoryEntry {
414 /// The source of the copy operation, which is also an entry of the directory walk.
415 ///
416 /// Note that its [`rela_path`](gix_dir::EntryRef::rela_path) is the source of the rewrite.
417 source_dirwalk_entry: gix_dir::Entry,
418 /// `collapsed_directory_status` is `Some(dir_status)` if this `source_dirwalk_entry` was part of a directory with the given
419 /// `dir_status` that wasn't the same as the one of `source_dirwalk_entry` and
420 /// if [gix_dir::walk::Options::emit_collapsed] was [CollapsedEntriesEmissionMode::OnStatusMismatch](gix_dir::walk::CollapsedEntriesEmissionMode::OnStatusMismatch).
421 /// It will also be `Some(dir_status)` if that option was [CollapsedEntriesEmissionMode::All](gix_dir::walk::CollapsedEntriesEmissionMode::All).
422 source_dirwalk_entry_collapsed_directory_status: Option<gix_dir::entry::Status>,
423 /// The object id as it would appear if the entry was written to the object database.
424 /// It's the same as [`dirwalk_entry_id`](Item::Rewrite), or `diff` is `Some(_)` to indicate that the copy
425 /// was determined by similarity, not by content equality.
426 source_dirwalk_entry_id: gix_hash::ObjectId,
427 },
428}
429
430///
431pub mod iter {
432 use gix_status::index_as_worktree::{Change, EntryStatus};
433 pub use gix_status::index_as_worktree_with_renames::Summary;
434
435 use super::{Item, RewriteSource};
436 use crate::{
437 bstr::{BStr, BString},
438 status::{index_worktree, Platform},
439 };
440
441 /// Access
442 impl RewriteSource {
443 /// The repository-relative path of this source.
444 pub fn rela_path(&self) -> &BStr {
445 match self {
446 RewriteSource::RewriteFromIndex { source_rela_path, .. } => source_rela_path.as_ref(),
447 RewriteSource::CopyFromDirectoryEntry {
448 source_dirwalk_entry, ..
449 } => source_dirwalk_entry.rela_path.as_ref(),
450 }
451 }
452 }
453
454 impl<'index> From<gix_status::index_as_worktree_with_renames::RewriteSource<'index, (), SubmoduleStatus>>
455 for RewriteSource
456 {
457 fn from(value: gix_status::index_as_worktree_with_renames::RewriteSource<'index, (), SubmoduleStatus>) -> Self {
458 match value {
459 gix_status::index_as_worktree_with_renames::RewriteSource::RewriteFromIndex {
460 index_entries: _,
461 source_entry,
462 source_entry_index,
463 source_rela_path,
464 source_status,
465 } => RewriteSource::RewriteFromIndex {
466 source_entry: source_entry.clone(),
467 source_entry_index,
468 source_rela_path: source_rela_path.to_owned(),
469 source_status,
470 },
471 gix_status::index_as_worktree_with_renames::RewriteSource::CopyFromDirectoryEntry {
472 source_dirwalk_entry,
473 source_dirwalk_entry_collapsed_directory_status,
474 source_dirwalk_entry_id,
475 } => RewriteSource::CopyFromDirectoryEntry {
476 source_dirwalk_entry,
477 source_dirwalk_entry_collapsed_directory_status,
478 source_dirwalk_entry_id,
479 },
480 }
481 }
482 }
483
484 impl Item {
485 /// Return a simplified summary of the item as digest of its status, or `None` if this item is
486 /// created from the directory walk and is *not untracked*, or if it is merely to communicate
487 /// a needed update to the index entry.
488 pub fn summary(&self) -> Option<Summary> {
489 use gix_status::index_as_worktree_with_renames::Summary::*;
490 Some(match self {
491 Item::Modification { status, .. } => match status {
492 EntryStatus::Conflict { .. } => Conflict,
493 EntryStatus::Change(change) => match change {
494 Change::Removed => Removed,
495 Change::Type { .. } => TypeChange,
496 Change::Modification { .. } | Change::SubmoduleModification(_) => Modified,
497 },
498 EntryStatus::NeedsUpdate(_) => return None,
499 EntryStatus::IntentToAdd => IntentToAdd,
500 },
501 Item::DirectoryContents { entry, .. } => {
502 if matches!(entry.status, gix_dir::entry::Status::Untracked) {
503 Added
504 } else {
505 return None;
506 }
507 }
508 Item::Rewrite { copy, .. } => {
509 if *copy {
510 Copied
511 } else {
512 Renamed
513 }
514 }
515 })
516 }
517
518 /// The repository-relative path of the entry contained in this item.
519 pub fn rela_path(&self) -> &BStr {
520 match self {
521 Item::Modification { rela_path, .. } => rela_path.as_ref(),
522 Item::DirectoryContents { entry, .. } => entry.rela_path.as_ref(),
523 Item::Rewrite { dirwalk_entry, .. } => dirwalk_entry.rela_path.as_ref(),
524 }
525 }
526 }
527
528 impl<'index> From<gix_status::index_as_worktree_with_renames::Entry<'index, (), SubmoduleStatus>> for Item {
529 fn from(value: gix_status::index_as_worktree_with_renames::Entry<'index, (), SubmoduleStatus>) -> Self {
530 match value {
531 gix_status::index_as_worktree_with_renames::Entry::Modification {
532 entries: _,
533 entry,
534 entry_index,
535 rela_path,
536 status,
537 } => Item::Modification {
538 entry: entry.clone(),
539 entry_index,
540 rela_path: rela_path.to_owned(),
541 status,
542 },
543 gix_status::index_as_worktree_with_renames::Entry::DirectoryContents {
544 entry,
545 collapsed_directory_status,
546 } => Item::DirectoryContents {
547 entry,
548 collapsed_directory_status,
549 },
550 gix_status::index_as_worktree_with_renames::Entry::Rewrite {
551 source,
552 dirwalk_entry,
553 dirwalk_entry_collapsed_directory_status,
554 dirwalk_entry_id,
555 diff,
556 copy,
557 } => Item::Rewrite {
558 source: source.into(),
559 dirwalk_entry,
560 dirwalk_entry_collapsed_directory_status,
561 dirwalk_entry_id,
562 diff,
563 copy,
564 },
565 }
566 }
567 }
568
569 type SubmoduleStatus = crate::submodule::Status;
570
571 /// Lifecycle
572 impl<Progress> Platform<'_, Progress>
573 where
574 Progress: gix_features::progress::Progress,
575 {
576 /// Turn the platform into an iterator for changes between the index and the working tree.
577 ///
578 /// * `patterns`
579 /// - Optional patterns to use to limit the paths to look at. If empty, all paths are considered.
580 #[doc(alias = "diff_index_to_workdir", alias = "git2")]
581 pub fn into_index_worktree_iter(
582 mut self,
583 patterns: impl IntoIterator<Item = BString>,
584 ) -> Result<index_worktree::Iter, crate::status::into_iter::Error> {
585 // deactivate the tree-iteration
586 self.head_tree = None;
587 Ok(index_worktree::Iter {
588 inner: self.into_iter(patterns)?,
589 })
590 }
591 }
592
593 impl Iterator for super::Iter {
594 type Item = Result<Item, index_worktree::Error>;
595
596 fn next(&mut self) -> Option<Self::Item> {
597 self.inner.next().map(|res| {
598 res.map(|item| match item {
599 crate::status::Item::IndexWorktree(item) => item,
600 crate::status::Item::TreeIndex(_) => unreachable!("BUG: we deactivated this kind of traversal"),
601 })
602 .map_err(|err| match err {
603 crate::status::iter::Error::IndexWorktree(err) => err,
604 crate::status::iter::Error::TreeIndex(_) => {
605 unreachable!("BUG: we deactivated this kind of traversal")
606 }
607 })
608 })
609 }
610 }
611
612 /// Access
613 impl super::Iter {
614 /// Return the outcome of the iteration, or `None` if the iterator isn't fully consumed.
615 pub fn outcome_mut(&mut self) -> Option<&mut crate::status::Outcome> {
616 self.inner.out.as_mut()
617 }
618
619 /// Turn the iterator into the iteration outcome, which is `None` on error or if the iteration
620 /// isn't complete.
621 pub fn into_outcome(mut self) -> Option<crate::status::Outcome> {
622 self.inner.out.take()
623 }
624 }
625}