Skip to main content

specfold_core/workspace/vcs/
repo.rs

1//! Running git, and finding the repository that holds a path.
2
3use std::path::{Path, PathBuf};
4use std::process::Command;
5
6pub(super) fn git(root: &Path, args: &[&str]) -> Option<Vec<u8>> {
7    let out = Command::new("git").arg("-C").arg(root).args(args).output().ok()?;
8    if !out.status.success() {
9        return None;
10    }
11    Some(out.stdout)
12}
13
14pub(super) fn split_nul(bytes: Vec<u8>) -> Vec<String> {
15    bytes.split(|b| *b == 0).filter(|s| !s.is_empty()).map(|s| String::from_utf8_lossy(s).into_owned()).collect()
16}
17
18pub fn git_root(dir: &Path) -> Option<PathBuf> {
19    let out = git(dir, &["rev-parse", "--show-toplevel"])?;
20    let s = String::from_utf8(out).ok()?;
21    Some(PathBuf::from(s.trim()))
22}
23
24pub fn is_repo(dir: &Path) -> bool {
25    git(dir, &["rev-parse", "--git-dir"]).is_some()
26}
27
28/// The repository that holds `path`: a submodule or nested checkout below `root` keeps its own
29/// history, which the outer repository cannot see. Returns that directory, the path relative to
30/// it, and the prefix between `root` and it.
31pub(super) fn nearest_repo(root: &Path, path: &str) -> (PathBuf, String, String) {
32    let mut prefix = Path::new(path).parent();
33    while let Some(dir) = prefix.filter(|d| !d.as_os_str().is_empty()) {
34        if root.join(dir).join(".git").exists() {
35            let p = dir.to_string_lossy().replace('\\', "/");
36            let rel = path.strip_prefix(&format!("{p}/")).unwrap_or(path).to_string();
37            return (root.join(dir), rel, format!("{p}/"));
38        }
39        prefix = dir.parent();
40    }
41    (root.to_path_buf(), path.to_string(), String::new())
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn nested_checkout_owns_its_paths() {
50        let tmp = tempfile::tempdir().unwrap();
51        let r = tmp.path();
52        std::fs::create_dir_all(r.join("libs/sub/.git")).unwrap();
53        std::fs::create_dir_all(r.join("libs/sub/docs")).unwrap();
54        let (repo, rel, prefix) = nearest_repo(r, "libs/sub/docs/a.md");
55        assert_eq!((repo, rel.as_str(), prefix.as_str()), (r.join("libs/sub"), "docs/a.md", "libs/sub/"));
56        let (repo, rel, prefix) = nearest_repo(r, "docs/a.md");
57        assert_eq!((repo, rel.as_str(), prefix.as_str()), (r.to_path_buf(), "docs/a.md", ""));
58    }
59}