gnostr_asyncgit/sync/
submodules.rs

1use std::path::{Path, PathBuf};
2
3pub use git2::SubmoduleStatus;
4use git2::{Repository, RepositoryOpenFlags, Submodule, SubmoduleUpdateOptions};
5use scopetime::scope_time;
6
7use super::{repo, CommitId, RepoPath};
8use crate::{error::Result, sync::utils::work_dir, Error};
9
10///
11#[derive(Debug)]
12pub struct SubmoduleInfo {
13    ///
14    pub name: String,
15    ///
16    pub path: PathBuf,
17    ///
18    pub url: Option<String>,
19    ///
20    pub id: Option<CommitId>,
21    ///
22    pub head_id: Option<CommitId>,
23    ///
24    pub status: SubmoduleStatus,
25}
26
27///
28#[derive(Debug)]
29pub struct SubmoduleParentInfo {
30    /// where to find parent repo
31    pub parent_gitpath: PathBuf,
32    /// where to find submodule git path
33    pub submodule_gitpath: PathBuf,
34    /// `submodule_info` from perspective of parent repo
35    pub submodule_info: SubmoduleInfo,
36}
37
38impl SubmoduleInfo {
39    ///
40    pub fn get_repo_path(&self, repo_path: &RepoPath) -> Result<RepoPath> {
41        let repo = repo(repo_path)?;
42        let wd = repo.workdir().ok_or(Error::NoWorkDir)?;
43
44        Ok(RepoPath::Path(wd.join(self.path.clone())))
45    }
46}
47
48fn submodule_to_info(s: &Submodule, r: &Repository) -> SubmoduleInfo {
49    let status = r
50        .submodule_status(s.name().unwrap_or_default(), git2::SubmoduleIgnore::None)
51        .unwrap_or(SubmoduleStatus::empty());
52
53    SubmoduleInfo {
54        name: s.name().unwrap_or_default().into(),
55        path: s.path().to_path_buf(),
56        id: s.workdir_id().map(CommitId::from),
57        head_id: s.head_id().map(CommitId::from),
58        url: s.url().map(String::from),
59        status,
60    }
61}
62
63///
64pub fn get_submodules(repo_path: &RepoPath) -> Result<Vec<SubmoduleInfo>> {
65    scope_time!("get_submodules");
66
67    let (r, repo2) = (repo(repo_path)?, repo(repo_path)?);
68
69    let res = r
70        .submodules()?
71        .iter()
72        .map(|s| submodule_to_info(s, &repo2))
73        .collect();
74
75    Ok(res)
76}
77
78///
79pub fn update_submodule(repo_path: &RepoPath, name: &str) -> Result<()> {
80    scope_time!("update_submodule");
81
82    let repo = repo(repo_path)?;
83
84    let mut submodule = repo.find_submodule(name)?;
85
86    let mut options = SubmoduleUpdateOptions::new();
87    options.allow_fetch(true);
88
89    submodule.update(true, Some(&mut options))?;
90
91    Ok(())
92}
93
94/// query whether `repo_path` points to a repo that is part of a
95/// parent git which contains it as a submodule
96pub fn submodule_parent_info(repo_path: &RepoPath) -> Result<Option<SubmoduleParentInfo>> {
97    scope_time!("submodule_parent_info");
98
99    let repo = repo(repo_path)?;
100    let repo_wd = work_dir(&repo)?.to_path_buf();
101
102    log::trace!("[sub] repo_wd: {:?}", repo_wd);
103    log::trace!("[sub] repo_path: {:?}", repo.path());
104
105    if let Some(parent_path) = repo_wd.parent() {
106        log::trace!("[sub] parent_path: {:?}", parent_path);
107
108        if let Ok(parent) = Repository::open_ext(
109            parent_path,
110            RepositoryOpenFlags::empty(),
111            Vec::<&Path>::new(),
112        ) {
113            let parent_wd = work_dir(&parent)?.to_path_buf();
114            log::trace!("[sub] parent_wd: {:?}", parent_wd);
115
116            let submodule_name = repo_wd
117                .strip_prefix(parent_wd)?
118                .to_string_lossy()
119                .to_string();
120
121            log::trace!("[sub] submodule_name: {:?}", submodule_name);
122
123            if let Ok(submodule) = parent.find_submodule(&submodule_name) {
124                return Ok(Some(SubmoduleParentInfo {
125                    parent_gitpath: parent.path().to_path_buf(),
126                    submodule_gitpath: repo.path().to_path_buf(),
127                    submodule_info: submodule_to_info(&submodule, &parent),
128                }));
129            }
130        }
131    }
132
133    Ok(None)
134}
135
136#[cfg(test)]
137mod tests {
138    use std::path::Path;
139
140    use git2::Repository;
141    use pretty_assertions::assert_eq;
142
143    use super::get_submodules;
144    use crate::sync::{submodules::submodule_parent_info, tests::repo_init, RepoPath};
145
146    #[test]
147    fn test_smoke() {
148        let (dir, _r) = repo_init().unwrap();
149
150        {
151            let r = Repository::open(dir.path()).unwrap();
152            let mut s = r
153                .submodule(
154                    //TODO: use local git
155                    "https://github.com/extrawurst/brewdump.git",
156                    Path::new("foo/bar"),
157                    false,
158                )
159                .unwrap();
160
161            let _sub_r = s.clone(None).unwrap();
162            s.add_finalize().unwrap();
163        }
164
165        let repo_p = RepoPath::Path(dir.into_path());
166        let subs = get_submodules(&repo_p).unwrap();
167
168        assert_eq!(subs.len(), 1);
169        assert_eq!(&subs[0].name, "foo/bar");
170
171        let info = submodule_parent_info(&subs[0].get_repo_path(&repo_p).unwrap())
172            .unwrap()
173            .unwrap();
174
175        dbg!(&info);
176
177        assert_eq!(&info.submodule_info.name, "foo/bar");
178    }
179}