Skip to content

Commit f01cf70

Browse files
committed
Add submodule support for status iterator
1 parent cee7fa2 commit f01cf70

File tree

6 files changed

+134
-15
lines changed

6 files changed

+134
-15
lines changed

gix/src/status/index_worktree.rs

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ impl Repository {
174174
/// Note that depending on the underlying configuration, there might be a significant delay until the first
175175
/// item is received due to the buffering necessary to perform rename tracking and/or sorting.
176176
///
177+
/// ### Submodules
178+
///
179+
/// Note that submodules can be set to 'inactive' which automatically excludes them from the status operation.
180+
///
177181
/// ### Index Changes
178182
///
179183
/// Changes to the index are collected and it's possible to write the index back using [iter::Outcome::write_changes()].
@@ -200,6 +204,7 @@ pub mod iter {
200204
use crate::status::index_worktree::iter;
201205
use crate::status::{index_worktree, Platform};
202206
use crate::worktree::IndexPersistedOrInMemory;
207+
use crate::ThreadSafeRepository;
203208
use std::sync::atomic::{AtomicBool, Ordering};
204209
use std::sync::Arc;
205210

@@ -420,9 +425,7 @@ pub mod iter {
420425
}
421426
}
422427

423-
/// The status of a submodule
424-
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
425-
pub struct SubmoduleStatus {}
428+
type SubmoduleStatus = crate::submodule::Status;
426429

427430
/// The error returned by [Platform::into_index_worktree_iter()](crate::status::Platform::into_index_worktree_iter()).
428431
#[derive(Debug, thiserror::Error)]
@@ -434,6 +437,8 @@ pub mod iter {
434437
SpawnThread(#[source] std::io::Error),
435438
#[error(transparent)]
436439
ConfigSkipHash(#[from] crate::config::boolean::Error),
440+
#[error(transparent)]
441+
PrepareSubmodules(#[from] crate::submodule::modules::Error),
437442
}
438443

439444
/// Lifecycle
@@ -454,7 +459,6 @@ pub mod iter {
454459
let should_interrupt = Arc::new(AtomicBool::default());
455460
let (tx, rx) = std::sync::mpsc::channel();
456461
let mut collect = Collect { tx };
457-
let submodule = ComputeSubmoduleStatus { _mode: self.submodules };
458462
let skip_hash = self
459463
.repo
460464
.config
@@ -464,6 +468,7 @@ pub mod iter {
464468
.transpose()
465469
.with_lenient_default(self.repo.config.lenient_config)?
466470
.unwrap_or_default();
471+
let submodule = ComputeSubmoduleStatus::new(self.repo.clone().into_sync(), self.submodules)?;
467472
let join = std::thread::Builder::new()
468473
.name("gix::status::index_worktree::iter::producer".into())
469474
.spawn({
@@ -568,19 +573,53 @@ pub mod iter {
568573

569574
#[derive(Clone)]
570575
struct ComputeSubmoduleStatus {
571-
_mode: crate::status::Submodule,
576+
mode: crate::status::Submodule,
577+
repo: ThreadSafeRepository,
578+
submodule_paths: Vec<BString>,
572579
}
573580

581+
///
574582
mod submodule_status {
583+
use crate::bstr;
575584
use crate::bstr::BStr;
576585
use crate::status::index_worktree::iter::{ComputeSubmoduleStatus, SubmoduleStatus};
586+
use crate::status::Submodule;
587+
use std::borrow::Cow;
588+
589+
impl ComputeSubmoduleStatus {
590+
pub(super) fn new(
591+
repo: crate::ThreadSafeRepository,
592+
mode: crate::status::Submodule,
593+
) -> Result<Self, crate::submodule::modules::Error> {
594+
let local_repo = repo.to_thread_local();
595+
let submodule_paths = match local_repo.submodules()? {
596+
Some(sm) => {
597+
let mut v: Vec<_> = sm
598+
.filter(|sm| sm.is_active().unwrap_or_default())
599+
.filter_map(|sm| sm.path().ok().map(Cow::into_owned))
600+
.collect();
601+
v.sort();
602+
v
603+
}
604+
None => Vec::new(),
605+
};
606+
Ok(Self {
607+
mode,
608+
repo,
609+
submodule_paths,
610+
})
611+
}
612+
}
577613

578-
/// The error returned by the submodule status implementation - it will be turned into a box, hence it doesn't have to
579-
/// be private.
614+
/// The error returned submodule status checks.
580615
#[derive(Debug, thiserror::Error)]
581616
#[allow(missing_docs)]
582-
#[error("TBD")]
583-
pub enum Error {}
617+
pub(super) enum Error {
618+
#[error(transparent)]
619+
SubmoduleStatus(#[from] crate::submodule::status::Error),
620+
#[error(transparent)]
621+
IgnoreConfig(#[from] crate::submodule::config::Error),
622+
}
584623

585624
impl gix_status::index_as_worktree::traits::SubmoduleStatus for ComputeSubmoduleStatus {
586625
type Output = SubmoduleStatus;
@@ -589,9 +628,29 @@ pub mod iter {
589628
fn status(
590629
&mut self,
591630
_entry: &gix_index::Entry,
592-
_rela_path: &BStr,
631+
rela_path: &BStr,
593632
) -> Result<Option<Self::Output>, Self::Error> {
594-
todo!("impl in Submodule itself, it will be calling this exact implementation under the hood, maybe with reduced threads")
633+
use bstr::ByteSlice;
634+
if self
635+
.submodule_paths
636+
.binary_search_by(|path| path.as_bstr().cmp(rela_path))
637+
.is_err()
638+
{
639+
return Ok(None);
640+
}
641+
let repo = self.repo.to_thread_local();
642+
let Ok(Some(mut submodules)) = repo.submodules() else {
643+
return Ok(None);
644+
};
645+
let Some(sm) = submodules.find(|sm| sm.path().map_or(false, |path| path == rela_path)) else {
646+
return Ok(None);
647+
};
648+
let (ignore, check_dirty) = match self.mode {
649+
Submodule::AsConfigured { check_dirty } => (sm.ignore()?.unwrap_or_default(), check_dirty),
650+
Submodule::Given { ignore, check_dirty } => (ignore, check_dirty),
651+
};
652+
let status = sm.status(ignore, check_dirty)?;
653+
Ok(status.is_dirty().and_then(|dirty| dirty.then_some(status)))
595654
}
596655
}
597656
}

gix/src/status/mod.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ impl Repository {
6464
/// Whereas Git runs the index-modified check before the directory walk to set entries
6565
/// as up-to-date to (potentially) safe some disk-access, we run both in parallel which
6666
/// ultimately is much faster.
67-
// TODO: if untracked and ignored entries are disabled, don't run a dirwalk at all.
68-
// TODO: submodule support with reasonable configurability.
6967
pub fn status<P>(&self, progress: P) -> Result<Platform<'_, P>, config::boolean::Error>
7068
where
7169
P: gix_features::progress::Progress + 'static,
@@ -83,8 +81,6 @@ impl Repository {
8381
},
8482
})
8583
}
86-
87-
// TODO: submodule status, where the base of the operation is the list of submodules in the .gitmodules file.
8884
}
8985

9086
mod platform;
Binary file not shown.

gix/tests/fixtures/make_submodules.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ git init submodule-head-changed-and-modified
4242
)
4343
)
4444

45+
git init modified-untracked-and-submodule-head-changed-and-modified
46+
(cd modified-untracked-and-submodule-head-changed-and-modified
47+
git submodule add ../module1 m1
48+
git commit -m "add submodule"
49+
50+
(cd m1
51+
git checkout @~1
52+
echo change >> this
53+
)
54+
55+
touch this
56+
git add this && git commit -m "this"
57+
echo change >> this
58+
touch untracked
59+
)
60+
4561
git init with-submodules
4662
(cd with-submodules
4763
mkdir dir

gix/tests/gix.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ mod remote;
1616
mod repository;
1717
#[cfg(feature = "revision")]
1818
mod revision;
19+
#[cfg(feature = "status")]
20+
mod status;
1921
#[cfg(feature = "attributes")]
2022
mod submodule;

gix/tests/status/mod.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
pub fn repo(name: &str) -> crate::Result<gix::Repository> {
2+
use crate::util::named_subrepo_opts;
3+
Ok(named_subrepo_opts(
4+
"make_submodules.sh",
5+
name,
6+
gix::open::Options::isolated(),
7+
)?)
8+
}
9+
10+
mod index_worktree {
11+
mod iter {
12+
use crate::status::repo;
13+
14+
#[test]
15+
fn submodule_modification() -> crate::Result {
16+
let repo = repo("modified-untracked-and-submodule-head-changed-and-modified")?;
17+
let mut status = repo
18+
.status(gix::progress::Discard)?
19+
.index_worktree_options_mut(|opts| {
20+
opts.sorting =
21+
Some(gix::status::plumbing::index_as_worktree_with_renames::Sorting::ByPathCaseSensitive)
22+
})
23+
.into_index_worktree_iter(Vec::new())?;
24+
let items: Vec<_> = status.by_ref().filter_map(Result::ok).collect();
25+
assert_eq!(items.len(), 3, "1 untracked, 1 modified file, 1 submodule modification");
26+
Ok(())
27+
}
28+
29+
#[test]
30+
fn early_drop_for_is_dirty_emulation() -> crate::Result {
31+
let repo = repo("modified-untracked-and-submodule-head-changed-and-modified")?;
32+
let is_dirty = repo
33+
.status(gix::progress::Discard)?
34+
.index_worktree_submodules(gix::status::Submodule::AsConfigured { check_dirty: true })
35+
.index_worktree_options_mut(|opts| {
36+
opts.sorting =
37+
Some(gix::status::plumbing::index_as_worktree_with_renames::Sorting::ByPathCaseSensitive)
38+
})
39+
.into_index_worktree_iter(Vec::new())?
40+
.next()
41+
.is_some();
42+
assert!(is_dirty, "this should abort the work as quickly as possible");
43+
Ok(())
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)