submodule_kit/
submodule.rs1use crate::strings;
2use std::path::Path;
3
4#[derive(Debug)]
5pub struct SubmoduleInfo {
6 pub path: String,
7 pub url: String,
8 pub branch: Option<String>,
9}
10
11pub fn parse_gitmodules_str(content: &str) -> Result<Vec<SubmoduleInfo>, String> {
12 let mut submodules: Vec<SubmoduleInfo> = Vec::new();
13 let mut current_name: Option<String> = None;
14 let mut current_path: Option<String> = None;
15 let mut current_url: Option<String> = None;
16 let mut current_branch: Option<String> = None;
17
18 let flush = |name: String,
19 path: Option<String>,
20 url: Option<String>,
21 branch: Option<String>,
22 out: &mut Vec<SubmoduleInfo>|
23 -> Result<(), String> {
24 let path = path.ok_or_else(|| strings::err_missing_path(&name))?;
25 let url = url.ok_or_else(|| strings::err_missing_url(&name))?;
26 out.push(SubmoduleInfo { path, url, branch });
27 Ok(())
28 };
29
30 for line in content.lines() {
31 let line = line.trim();
32 if line.starts_with(strings::SUBMODULE_SECTION_CHECK) && line.ends_with(']') {
33 if let Some(name) = current_name.take() {
34 flush(
35 name,
36 current_path.take(),
37 current_url.take(),
38 current_branch.take(),
39 &mut submodules,
40 )?;
41 }
42 current_name = Some(
43 line.trim_start_matches(strings::SUBMODULE_SECTION_PREFIX)
44 .trim_end_matches(strings::SUBMODULE_SECTION_SUFFIX)
45 .to_string(),
46 );
47 } else if let Some(v) = line.strip_prefix(strings::KEY_PATH) {
48 current_path = Some(v.trim().to_string());
49 } else if let Some(v) = line.strip_prefix(strings::KEY_URL) {
50 current_url = Some(v.trim().to_string());
51 } else if let Some(v) = line.strip_prefix(strings::KEY_BRANCH) {
52 current_branch = Some(v.trim().to_string());
53 }
54 }
55
56 if let Some(name) = current_name.take() {
57 flush(
58 name,
59 current_path.take(),
60 current_url.take(),
61 current_branch.take(),
62 &mut submodules,
63 )?;
64 }
65
66 Ok(submodules)
67}
68
69pub fn parse_gitmodules() -> Result<Vec<SubmoduleInfo>, String> {
70 let content = std::fs::read_to_string(strings::GITMODULES_FILE)
71 .map_err(|e| strings::err_read_gitmodules(&e))?;
72 parse_gitmodules_str(&content)
73}
74
75pub fn git_rev_parse_submodule(repo: &git2::Repository, path: &str) -> Result<String, String> {
76 let index = repo.index().map_err(|e| strings::err_open_index(&e))?;
77 let entry = index
78 .get_path(Path::new(path), 0)
79 .ok_or_else(|| strings::err_not_in_index(path))?;
80 Ok(entry.id.to_string())
81}
82
83pub fn git_ls_remote(_repo: &git2::Repository, url: &str, branch: &str) -> Result<String, String> {
84 let refspec = format!("{}{branch}", strings::REFS_HEADS_PREFIX);
85 let output = std::process::Command::new("git")
86 .args(["ls-remote", url, &refspec])
87 .output()
88 .map_err(|e| strings::err_connect_remote(url, &e))?;
89 if !output.status.success() {
90 let stderr = String::from_utf8_lossy(&output.stderr);
91 return Err(strings::err_connect_remote(url, &stderr.trim()));
92 }
93 let stdout = String::from_utf8_lossy(&output.stdout);
94 stdout
95 .lines()
96 .find(|line| line.ends_with(&refspec))
97 .and_then(|line| line.split_whitespace().next())
98 .map(|sha| sha.to_string())
99 .ok_or_else(|| strings::err_ref_not_found(&refspec, url))
100}
101
102pub fn short(sha: &str) -> &str {
103 &sha[..sha.len().min(7)]
104}
105
106#[cfg(test)]
107#[path = "submodule_tests.rs"]
108mod tests;