Skip to main content

pgit_native/
fetch.rs

1use std::path::{Path, PathBuf};
2use std::process::Command;
3
4use anyhow::{Context, Result};
5
6use pgit_core::purl::Purl;
7
8/// Resolve the git clone URL from a PURL namespace.
9///
10/// Namespace format: `github.com/owner/repo` or `gitlab.com/owner/repo`
11pub fn url_from_purl(purl: &Purl) -> Result<String> {
12    let ns = purl
13        .namespace
14        .as_deref()
15        .with_context(|| format!("PURL '{}' has no namespace (registry)", purl))?;
16
17    // namespace = host/owner/repo — reconstruct HTTPS clone URL
18    Ok(format!("https://{}.git", ns))
19}
20
21/// Shallow-clone a git repository into `dest/<dir_name>`.
22///
23/// Passes `GIT_LFS_SKIP_SMUDGE=0` so LFS pointers are smudged automatically
24/// when git-lfs is available.
25pub fn shallow_clone(
26    url: &str,
27    git_ref: Option<&str>,
28    dir_name: &str,
29    dest: &Path,
30) -> Result<PathBuf> {
31    let clone_dir = dest.join(dir_name);
32    let clone_str = clone_dir
33        .to_str()
34        .with_context(|| "clone path is not valid UTF-8")?;
35
36    let mut args: Vec<&str> = vec!["clone", "--depth", "1"];
37    if let Some(r) = git_ref {
38        args.extend_from_slice(&["--branch", r]);
39    }
40    args.push(url);
41    args.push(clone_str);
42
43    let output = Command::new("git")
44        .args(&args)
45        .env_remove("GIT_DIR")
46        .env_remove("GIT_WORK_TREE")
47        .env_remove("GIT_INDEX_FILE")
48        .env("GIT_LFS_SKIP_SMUDGE", "0")
49        .output()
50        .context("failed to run git clone")?;
51
52    if !output.status.success() {
53        let stderr = String::from_utf8_lossy(&output.stderr);
54        anyhow::bail!("git clone failed: {}", stderr.trim());
55    }
56
57    Ok(clone_dir)
58}
59
60/// Resolve the full commit SHA of HEAD in a cloned repository.
61pub fn resolve_head_sha(repo: &Path) -> Result<String> {
62    let output = Command::new("git")
63        .args(["rev-parse", "HEAD"])
64        .current_dir(repo)
65        .env_remove("GIT_DIR")
66        .output()
67        .context("failed to run git rev-parse")?;
68
69    if !output.status.success() {
70        anyhow::bail!("git rev-parse failed");
71    }
72
73    Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
74}
75
76/// Return the path to `subfolder` inside `clone_dir`, or an error if missing.
77pub fn resolve_subfolder(clone_dir: &Path, subfolder: Option<&str>) -> Result<PathBuf> {
78    match subfolder {
79        None => Ok(clone_dir.to_path_buf()),
80        Some(sub) => {
81            let path = clone_dir.join(sub);
82            anyhow::ensure!(
83                path.is_dir(),
84                "subfolder '{}' not found in cloned repo",
85                sub
86            );
87            Ok(path)
88        }
89    }
90}
91
92/// Remove a temporary directory, ignoring errors.
93pub fn cleanup(path: &Path) {
94    let _ = std::fs::remove_dir_all(path);
95}