pub mod git {
use std::path::Path;
use anyhow::Context;
pub fn find_latest_hash(repository_url: &str, branch_name: &str) -> anyhow::Result<String> {
let check_revision_hash = std::process::Command::new("git")
.arg("ls-remote")
.arg(repository_url)
.arg("--branch")
.arg(branch_name)
.stderr(std::process::Stdio::inherit())
.stdout(std::process::Stdio::piped())
.output()
.context(format!(
"failed to fetch latest git rev-hash from branch {branch_name}, is git installed?.",
))?;
let revision_hash: String = String::from_utf8(check_revision_hash.stdout)
.context(format!(
"failed to format latest git rev-hash from branch {branch_name}, does the branch \
exist?.",
))?
.chars()
.take_while(|&c| c != '\t')
.collect();
Ok(revision_hash)
}
#[allow(dead_code)]
pub fn clone_specific_revision(
repository_url: &str,
revision: &str,
dir: &Path,
) -> anyhow::Result<()> {
std::process::Command::new("git")
.arg("clone")
.args(["--revision", revision])
.arg("--depth=1")
.arg("--")
.arg(repository_url)
.arg(dir)
.stderr(std::process::Stdio::inherit())
.stdout(std::process::Stdio::inherit())
.spawn()
.context("Failed to spawn shell for git command")?
.wait()
.with_context(|| {
format!("failed to clone {revision} of {repository_url} to {}", dir.display())
})?;
Ok(())
}
}
pub mod fs {
use std::{
fs,
path::{Path, PathBuf},
time::SystemTime,
};
use anyhow::Context;
#[cfg(unix)]
pub fn symlink(from: &std::path::Path, to: &std::path::Path) -> anyhow::Result<()> {
std::os::unix::fs::symlink(to, from).context("could not create symlink")
}
#[cfg(windows)]
pub fn symlink(from: &std::path::Path, to: &std::path::Path) -> anyhow::Result<()> {
std::os::windows::fs::symlink_file(to, from).context("could not create symlink")
}
const ENTRY_LIMIT: u32 = u32::MAX;
pub fn latest_modification(dir: &Path) -> anyhow::Result<(SystemTime, PathBuf)> {
fn traverse_directories(
dir: &Path,
latest: Option<(SystemTime, PathBuf)>,
current_entry: u32,
) -> (Option<(SystemTime, PathBuf)>, u32) {
let mut local_latest = latest;
let mut current_entry_count = current_entry;
let entries = fs::read_dir(dir);
if let Ok(entries) = entries {
for file in entries {
let Ok(file) = file else {
continue;
};
let Ok(metadata) = file.metadata() else {
continue;
};
if current_entry_count == ENTRY_LIMIT {
break;
}
let (current_entry_latest, visited_entries) =
if metadata.is_dir() && !metadata.is_symlink() {
traverse_directories(&file.path(), local_latest.clone(), current_entry_count)
} else {
(metadata.modified().ok().map(|metadata| (metadata, file.path())), current_entry_count + 1)
};
current_entry_count = visited_entries;
local_latest = match (&local_latest, current_entry_latest) {
(
Some((local_latest_time, path_old)),
Some((current_entry_latest, path)),
) => {
if current_entry_latest > *local_latest_time {
Some((current_entry_latest, path))
} else {
Some((*local_latest_time, path_old.to_path_buf()))
}
},
(Some(local_latest), None) => Some(local_latest.clone()),
(None, Some(current_entry_latest)) => Some(current_entry_latest),
(None, None) => None,
};
}
} else {
println!("Failed to open {}, skipping it.", dir.display());
}
(local_latest, current_entry_count)
}
let directory_last_modification = dir
.metadata()
.and_then(|file| file.modified())
.map(|metadata| (metadata, dir.to_path_buf()))
.ok();
let (latest_found_modification, _) =
traverse_directories(dir, directory_last_modification, 0);
latest_found_modification.context("Failed to read any file")
}
pub fn copy_dir_recursive(src: &Path, dst: &Path, skip: &[&str]) -> anyhow::Result<()> {
for entry in fs::read_dir(src)
.with_context(|| format!("failed to read directory '{}'", src.display()))?
{
let entry =
entry.with_context(|| format!("failed to read entry in '{}'", src.display()))?;
let file_name = entry.file_name();
if file_name.to_str().is_some_and(|name| skip.contains(&name)) {
continue;
}
let file_type = entry
.file_type()
.with_context(|| format!("failed to stat entry '{}'", entry.path().display()))?;
let target = dst.join(&file_name);
if file_type.is_symlink() {
let link_target = fs::read_link(entry.path()).with_context(|| {
format!("failed to read symlink '{}'", entry.path().display())
})?;
symlink(&target, &link_target).with_context(|| {
format!(
"failed to recreate symlink '{}' -> '{}'",
target.display(),
link_target.display()
)
})?;
} else if file_type.is_dir() {
fs::create_dir_all(&target).with_context(|| {
format!("failed to create directory '{}'", target.display())
})?;
copy_dir_recursive(&entry.path(), &target, skip)?;
} else {
fs::copy(entry.path(), &target).with_context(|| {
format!("failed to copy '{}' to '{}'", entry.path().display(), target.display())
})?;
}
}
Ok(())
}
}