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