gitoxide_core/repository/
submodule.rs

1use anyhow::bail;
2use gix::{commit::describe::SelectRef, prelude::ObjectIdExt, Repository, Submodule};
3
4use crate::OutputFormat;
5
6pub fn list(
7    repo: Repository,
8    mut out: impl std::io::Write,
9    format: OutputFormat,
10    dirty_suffix: Option<String>,
11) -> anyhow::Result<()> {
12    if format != OutputFormat::Human {
13        bail!("Only human output is supported for now")
14    }
15
16    let Some(submodules) = repo.submodules()? else {
17        return Ok(());
18    };
19    for sm in submodules {
20        print_sm(sm, dirty_suffix.as_deref(), &mut out)?;
21    }
22    Ok(())
23}
24
25fn print_sm(sm: Submodule<'_>, dirty_suffix: Option<&str>, out: &mut impl std::io::Write) -> anyhow::Result<()> {
26    let _span = gix::trace::coarse!("print_sm", path = ?sm.path());
27    let state = sm.state()?;
28    let mut sm_repo = sm.open()?;
29    if let Some(repo) = sm_repo.as_mut() {
30        repo.object_cache_size_if_unset(4 * 1024 * 1024);
31    }
32    writeln!(
33        out,
34        " {is_active} {path} {config} head:{head_id} index:{index_id} ({worktree}) [{url}]",
35        is_active = if !sm.is_active()? || !state.repository_exists {
36            "ⅹ"
37        } else {
38            "✓"
39        },
40        path = sm.path()?,
41        config = if state.superproject_configuration {
42            "config:yes"
43        } else {
44            "config:no"
45        },
46        head_id = submodule_short_hash(sm.head_id()?, sm_repo.as_ref()),
47        index_id = submodule_short_hash(sm.index_id()?, sm_repo.as_ref()),
48        worktree = match sm_repo {
49            Some(repo) => {
50                // TODO(name-revision): this is the simple version, `git` gives it
51                // multiple tries https://github.com/git/git/blob/fac96dfbb1c24369ba7d37a5affd8adfe6c650fd/builtin/submodule--helper.c#L161
52                // and even uses `git name-rev`/`git describe --contains` which we can't do yet.
53                repo.head_commit()?
54                    .describe()
55                    .names(SelectRef::AllRefs)
56                    .id_as_fallback(true)
57                    .try_resolve()?
58                    .expect("resolution present if ID can be used as fallback")
59                    .format_with_dirty_suffix(dirty_suffix.map(ToOwned::to_owned))?
60                    .to_string()
61            }
62            None => {
63                "no worktree".to_string()
64            }
65        },
66        url = sm.url()?.to_bstring()
67    )?;
68    Ok(())
69}
70
71fn submodule_short_hash(id: Option<gix::ObjectId>, repo: Option<&Repository>) -> String {
72    id.map_or_else(
73        || "none".to_string(),
74        |id| repo.map_or_else(|| id.to_string(), |repo| id.attach(repo).shorten_or_id().to_string()),
75    )
76}