use std::io;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
use tokio::process::Command;
use tracing::{debug, trace};
use crate::error::Error;
use crate::metadata::{Metadata, get_crate_metadata_from_workspace};
use crate::utils::{self, DirectoryContents, get_contents};
use crate::workarounds::find_in_vcs;
pub(crate) async fn git_clone(url: &str, sha1: &str) -> Result<TempDir, Error> {
let out = TempDir::new()?;
let path = out.path().to_string_lossy();
let cmd = ["clone", url, path.as_ref(), "--revision", sha1, "--depth", "1"];
debug!("Cloning git repository: {}", url);
trace!("Cloning git repository into: {}", path);
let result = Command::new("git")
.args(cmd.iter())
.env("GIT_TERMINAL_PROMPT", "0")
.output()
.await?;
if !result.status.success() {
let stdout = String::from_utf8_lossy(&result.stdout).to_string();
let stderr = String::from_utf8_lossy(&result.stderr).to_string();
if stderr.contains("terminal prompts disabled") {
return Err(Error::InvalidRepoUrl {
repo: String::from(url),
});
}
if stderr.contains("dumb http transport") {
return Err(Error::InvalidRepoUrl {
repo: String::from(url),
});
}
if stderr.contains(&format!("not our ref {sha1}")) {
return Err(Error::InvalidGitRef {
repo: String::from(url),
rev: String::from(sha1),
});
}
return Err(Error::Subprocess {
cmd: cmd.join(" "),
stdout,
stderr,
});
}
Ok(out)
}
#[derive(Debug)]
#[non_exhaustive]
pub struct Repository<'a> {
pub metadata: Metadata,
pub root: PathBuf,
pub id: &'a str,
pub path_in_vcs: String,
_temp: Option<TempDir>,
}
impl<'a> Repository<'a> {
pub(crate) async fn clone(url: &str, id: &'a str, path_in_vcs: Option<&str>, name: &str) -> Result<Self, Error> {
let temp = git_clone(url, id).await?;
let (path_in_vcs, metadata) = if let Some(path_in_vcs) = path_in_vcs {
let metadata = get_crate_metadata_from_workspace(temp.as_ref(), path_in_vcs, name)?;
(path_in_vcs.to_string(), metadata)
} else {
debug!("Using heuristics to find crate in repository");
let Some((detected_path_in_vcs, metadata)) = find_in_vcs(&temp, name) else {
return Err(Error::PathNotDeterminable {
repo: url.to_string(),
name: name.to_string(),
});
};
debug!("Crate found at: '{detected_path_in_vcs}'");
(detected_path_in_vcs, metadata)
};
Ok(Repository {
metadata,
root: temp.path().to_owned(),
id,
path_in_vcs,
_temp: Some(temp),
})
}
pub(crate) fn cargo_toml(&self) -> PathBuf {
self.root.join(&self.path_in_vcs).join("Cargo.toml")
}
pub(crate) fn file_contents(&self) -> Result<DirectoryContents, Error> {
let mut contents = get_contents(&self.root.join(&self.path_in_vcs), &self.root)?;
contents
.files
.retain(|f| f.file_name().is_some_and(|s| s != "Cargo.toml"));
Ok(contents)
}
pub(crate) fn read_entry_to_bytes<P: AsRef<Path>>(&self, path: P) -> io::Result<Vec<u8>> {
trace!("Reading bytes from file: {}", path.as_ref().to_string_lossy());
utils::read_to_bytes(self.root.join(&self.path_in_vcs).join(path))
}
pub(crate) fn read_entry_to_string<P: AsRef<Path>>(&self, path: P) -> io::Result<String> {
trace!("Reading text from file: {}", path.as_ref().to_string_lossy());
std::fs::read_to_string(self.root.join(&self.path_in_vcs).join(path))
}
}