@@ -275,6 +275,182 @@ impl<'repo> Submodule<'repo> {
275
275
}
276
276
}
277
277
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
+
278
454
/// A summary of the state of all parts forming a submodule, which allows to answer various questions about it.
279
455
///
280
456
/// Note that expensive questions about its presence in the `HEAD` or the `index` are left to the caller.
0 commit comments