@@ -275,6 +275,182 @@ impl<'repo> Submodule<'repo> {
275275 }
276276}
277277
278+ ///
279+ #[ cfg( all( feature = "status" , feature = "parallel" ) ) ]
280+ pub mod status {
281+ use super :: { head_id, index_id, open, Status } ;
282+ use crate :: Submodule ;
283+ use gix_submodule:: config;
284+
285+ /// How to obtain a submodule's status.
286+ #[ derive( Copy , Clone , Debug , Ord , PartialOrd , Eq , PartialEq , Hash ) ]
287+ pub enum Mode {
288+ /// Use the ['ignore' value](crate::Submodule::ignore) to determine which submodules
289+ /// participate in the status query, and to which extent.
290+ AsConfigured {
291+ /// If `true`, default `false`, the computation will stop once the first in a ladder operations
292+ /// ordered from cheap to expensive shows that the submodule is dirty.
293+ /// Thus, submodules that are clean will still impose the complete set of computation, as configured.
294+ check_dirty : bool ,
295+ } ,
296+ /// Instead of the configuration, use the given ['ignore' value](crate::submodule::config::Ignore).
297+ /// This makes it possible to fine-tune the amount of work invested in this status, while allowing
298+ /// to turn off all submodule status information.
299+ Given {
300+ /// The portion of the submodule status to ignore.
301+ ignore : crate :: submodule:: config:: Ignore ,
302+ /// If `true`, default `false`, the computation will stop once the first in a ladder operations
303+ /// ordered from cheap to expensive shows that the submodule is dirty.
304+ /// Thus, submodules that are clean will still impose the complete set of computation, as given.
305+ check_dirty : bool ,
306+ } ,
307+ }
308+
309+ impl Default for Mode {
310+ fn default ( ) -> Self {
311+ Mode :: AsConfigured { check_dirty : false }
312+ }
313+ }
314+
315+ /// The error returned by [Submodule::status()].
316+ #[ derive( Debug , thiserror:: Error ) ]
317+ #[ allow( missing_docs) ]
318+ pub enum Error {
319+ #[ error( transparent) ]
320+ State ( #[ from] config:: path:: Error ) ,
321+ #[ error( transparent) ]
322+ HeadId ( #[ from] head_id:: Error ) ,
323+ #[ error( transparent) ]
324+ IndexId ( #[ from] index_id:: Error ) ,
325+ #[ error( transparent) ]
326+ OpenRepository ( #[ from] open:: Error ) ,
327+ #[ error( transparent) ]
328+ IgnoreConfiguration ( #[ from] config:: Error ) ,
329+ #[ error( transparent) ]
330+ StatusPlatform ( #[ from] crate :: config:: boolean:: Error ) ,
331+ #[ error( transparent) ]
332+ Status ( #[ from] crate :: status:: index_worktree:: iter:: Error ) ,
333+ }
334+
335+ impl < ' repo > Submodule < ' repo > {
336+ /// Return the status of the submodule based on the `mode`.
337+ ///
338+ /// The status allows to easily determine if a submodule [has changes](Status::is_dirty).
339+ #[ doc( alias = "submodule_status" , alias = "git2" ) ]
340+ pub fn status ( & self , mode : Mode ) -> Result < Status , Error > {
341+ let mut state = self . state ( ) ?;
342+ let ( ignore, check_dirty) = match mode {
343+ Mode :: Given { ignore, check_dirty } => ( ignore, check_dirty) ,
344+ Mode :: AsConfigured { check_dirty } => ( self . ignore ( ) ?. unwrap_or_default ( ) , check_dirty) ,
345+ } ;
346+ if ignore == config:: Ignore :: All {
347+ return Ok ( Status {
348+ state,
349+ ..Default :: default ( )
350+ } ) ;
351+ }
352+
353+ let index_id = self . index_id ( ) ?;
354+ if !state. repository_exists {
355+ return Ok ( Status {
356+ state,
357+ index_id,
358+ ..Default :: default ( )
359+ } ) ;
360+ }
361+ let sm_repo = match self . open ( ) ? {
362+ None => {
363+ state. repository_exists = false ;
364+ return Ok ( Status {
365+ state,
366+ index_id,
367+ ..Default :: default ( )
368+ } ) ;
369+ }
370+ Some ( repo) => repo,
371+ } ;
372+
373+ let checked_out_head_id = sm_repo. head_id ( ) . ok ( ) . map ( crate :: Id :: detach) ;
374+ let mut status = Status {
375+ state,
376+ index_id,
377+ checked_out_head_id,
378+ ..Default :: default ( )
379+ } ;
380+ if ignore == config:: Ignore :: Dirty || check_dirty && status. is_dirty ( ) == Some ( true ) {
381+ return Ok ( status) ;
382+ }
383+
384+ status. changes = Some (
385+ sm_repo
386+ . status ( gix_features:: progress:: Discard ) ?
387+ // TODO: Run the full status, including tree->index once available.
388+ . index_worktree_options_mut ( |opts| {
389+ assert ! ( opts. dirwalk_options. is_some( ) , "BUG: it's supposed to be the default" ) ;
390+ if ignore == config:: Ignore :: Untracked {
391+ opts. dirwalk_options = None ;
392+ }
393+ } )
394+ . into_index_worktree_iter ( Vec :: new ( ) ) ?
395+ . filter_map ( Result :: ok)
396+ . collect ( ) ,
397+ ) ;
398+
399+ Ok ( status)
400+ }
401+ }
402+
403+ impl Status {
404+ /// Return `Some(true)` if the submodule status could be determined sufficiently and
405+ /// if there are changes that would render this submodule dirty.
406+ ///
407+ /// Return `Some(false)` if the submodule status could be determined and it has no changes
408+ /// at all.
409+ ///
410+ /// Return `None` if the repository clone or the worktree are missing entirely, which would leave
411+ /// it to the caller to determine if that's considered dirty or not.
412+ pub fn is_dirty ( & self ) -> Option < bool > {
413+ if !self . state . worktree_checkout && !self . state . repository_exists {
414+ return None ;
415+ }
416+ let is_dirty =
417+ self . checked_out_head_id != self . index_id || self . changes . as_ref ( ) . map_or ( false , |c| !c. is_empty ( ) ) ;
418+ Some ( is_dirty)
419+ }
420+ }
421+
422+ pub ( super ) mod types {
423+ use crate :: submodule:: State ;
424+
425+ /// A simplified status of the Submodule.
426+ ///
427+ /// As opposed to the similar-sounding [`State`], it is more exhaustive and potentially expensive to compute,
428+ /// particularly for submodules without changes.
429+ ///
430+ /// It's produced by [Submodule::status()](crate::Submodule::status()).
431+ #[ derive( Default , Clone , PartialEq , Debug ) ]
432+ pub struct Status {
433+ /// The cheapest part of the status that is always performed, to learn if the repository is cloned
434+ /// and if there is a worktree checkout.
435+ pub state : State ,
436+ /// The commit at which the submodule is supposed to be according to the super-project's index.
437+ /// `None` means the computation wasn't performed, or the submodule didn't exist in the super-project's index anymore.
438+ pub index_id : Option < gix_hash:: ObjectId > ,
439+ /// The commit-id of the `HEAD` at which the submodule is currently checked out.
440+ /// `None` if the computation wasn't performed as it was skipped early, or if no repository was available or
441+ /// if the HEAD could not be obtained or wasn't born.
442+ pub checked_out_head_id : Option < gix_hash:: ObjectId > ,
443+ /// The set of changes obtained from running something akin to `git status` in the submodule working tree.
444+ ///
445+ /// `None` if the computation wasn't performed as the computation was skipped early, or if no working tree was
446+ /// available or repository was available.
447+ pub changes : Option < Vec < crate :: status:: index_worktree:: iter:: Item > > ,
448+ }
449+ }
450+ }
451+ #[ cfg( all( feature = "status" , feature = "parallel" ) ) ]
452+ pub use status:: types:: Status ;
453+
278454/// A summary of the state of all parts forming a submodule, which allows to answer various questions about it.
279455///
280456/// Note that expensive questions about its presence in the `HEAD` or the `index` are left to the caller.
0 commit comments