#![allow(dead_code)]
use anyhow::Result;
use git2::Repository as Git2Repository;
use gix::open::Options;
use std::path::PathBuf;
pub fn get_current_git2_repository(
repo: Option<Git2Repository>,
) -> Result<Git2Repository, anyhow::Error> {
if let Some(r) = repo { Ok(r) } else {
let rep = Git2Repository::discover(".")
.map_err(|e| anyhow::anyhow!("Failed to discover repository: {e}"))?;
if rep.is_bare() {
return Err(anyhow::anyhow!("Bare repositories are not supported"));
}
Ok(rep)
}
}
pub fn repo_from_path(path: &PathBuf) -> Result<gix::Repository, anyhow::Error> {
let options = Options::isolated();
gix::ThreadSafeRepository::open_opts(path, options)
.map(|repo| repo.to_thread_local())
.map_err(|e| anyhow::anyhow!("Failed to open repository at {path:?}: {e}"))
}
pub fn get_current_repository() -> Result<gix::Repository, anyhow::Error> {
let options = Options::isolated();
Ok(gix::ThreadSafeRepository::open_opts(".", options)?.to_thread_local())
}
pub fn get_cwd() -> Result<PathBuf, anyhow::Error> {
std::env::current_dir()
.map_err(|e| anyhow::anyhow!("Failed to get current working directory: {e}"))
}
pub fn get_thread_local_repo(
repo: &gix::Repository,
) -> Result<gix::Repository, anyhow::Error> {
let safe_repo = repo.to_owned().into_sync().to_thread_local();
Ok(safe_repo)
}
pub fn get_main_repo(
repo: Option<&gix::Repository>,
) -> Result<gix::Repository, anyhow::Error> {
let repo = match repo {
Some(r) => r.to_owned(),
None => get_current_repository()?,
};
let super_repo = repo
.main_repo()
.map_err(|e| anyhow::anyhow!("Failed to get main repository: {e}"))?;
Ok(super_repo)
}
pub fn get_main_root(repo: Option<&gix::Repository>) -> Result<PathBuf, anyhow::Error> {
let repo = get_main_repo(repo)?;
let path = repo.path().to_path_buf();
if path.is_dir() {
Ok(path)
} else {
Err(anyhow::anyhow!(
"Failed to get main repository root: {}",
path.display()
))
}
}
pub fn get_current_branch(repo: Option<&gix::Repository>) -> Result<String, anyhow::Error> {
fn branch_from_repo(repo: &gix::Repository) -> Result<String, anyhow::Error> {
let head = repo.head()?;
if let Some(reference) = head.referent_name() {
return Ok(reference.as_bstr().to_string());
}
Err(anyhow::anyhow!("Failed to get current branch name"))
}
if let Some(r) = repo { branch_from_repo(r) } else {
let owned = get_current_repository()?;
branch_from_repo(&owned)
}
}
pub fn get_current_working_directory() -> Result<PathBuf, anyhow::Error> {
std::env::current_dir()
.map_err(|e| anyhow::anyhow!("Failed to get current working directory: {e}"))
}
pub fn path_to_string(path: &std::path::Path) -> Result<String, anyhow::Error> {
path.to_str()
.map(std::string::ToString::to_string)
.ok_or_else(|| anyhow::anyhow!("Path is not valid UTF-8"))
}
pub fn path_to_string_lossy(path: &std::path::Path) -> String {
let lossy = path.to_string_lossy();
eprintln!(
"Warning: Path contains non-UTF-8 characters, using lossy conversion: {lossy}"
);
lossy.to_string()
}
pub fn path_to_os_string(path: &std::path::Path) -> std::ffi::OsString {
path.as_os_str().to_owned()
}
pub fn set_path(path: std::ffi::OsString) -> Result<String, anyhow::Error> {
match path.to_str() {
Some(path) => Ok(path.to_string()),
None => {
Ok(path_to_string_lossy(&PathBuf::from(path))) }
}
}
pub fn name_from_url(url: &str) -> Result<String, anyhow::Error> {
if url.is_empty() {
return Err(anyhow::anyhow!("URL cannot be empty"));
}
let cleaned_url = url.trim_end_matches('/').trim_end_matches(".git");
cleaned_url
.split('/')
.next_back()
.map(std::string::ToString::to_string)
.ok_or_else(|| anyhow::anyhow!("Failed to extract name from URL"))
}
pub fn name_from_osstring(os_string: std::ffi::OsString) -> Result<String, anyhow::Error> {
osstring_to_string(os_string).and_then(|s| {
if s.contains('\0') {
return Err(anyhow::anyhow!("Name cannot contain null bytes"));
}
if s.trim().is_empty() {
return Err(anyhow::anyhow!("Name cannot be empty or whitespace-only"));
}
let sep = std::path::MAIN_SEPARATOR.to_string();
s.trim()
.split(&sep)
.last()
.map(std::string::ToString::to_string)
.ok_or_else(|| anyhow::anyhow!("Failed to extract name from OsString"))
})
}
pub fn osstring_to_string(os_string: std::ffi::OsString) -> Result<String, anyhow::Error> {
os_string
.into_string()
.map_err(|_| anyhow::anyhow!("Failed to convert OsString to String"))
}
pub fn get_sparse_paths(
sparse_paths: Option<Vec<String>>,
) -> Result<Option<Vec<String>>, anyhow::Error> {
let sparse_paths_vec = match sparse_paths {
Some(paths) => {
for path in &paths {
if path.contains('\0') {
return Err(anyhow::anyhow!(
"Invalid sparse path pattern: contains null byte"
));
}
}
Some(paths)
}
None => None,
};
Ok(sparse_paths_vec)
}
pub fn get_name(
name: Option<String>,
url: Option<String>,
path: Option<std::ffi::OsString>,
) -> Result<String, anyhow::Error> {
if let Some(name) = name {
let trimmed_name = name.trim().to_string();
if trimmed_name.is_empty() { get_name(None, url, path) } else { Ok(trimmed_name) }
} else if let Some(path) = path {
name_from_osstring(path)
} else if let Some(url) = url {
name_from_url(&url)
} else {
Err(anyhow::anyhow!("No valid name source provided"))
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn test_get_name_valid_name() {
assert_eq!(
get_name(Some("my-repo".to_string()), None, None).unwrap(),
"my-repo"
);
assert_eq!(
get_name(Some(" spaced-repo ".to_string()), None, None).unwrap(),
"spaced-repo"
);
}
#[test]
fn test_get_name_empty_name_fallback_url() {
assert_eq!(
get_name(
Some(" ".to_string()),
Some("https://github.com/user/repo.git".to_string()),
None
)
.unwrap(),
"repo"
);
}
#[test]
fn test_get_name_empty_name_fallback_path() {
assert_eq!(
get_name(
Some("".to_string()),
None,
Some(PathBuf::from_iter(["path", "to", "my-module"]).into_os_string())
)
.unwrap(),
"my-module"
);
}
#[test]
fn test_get_name_empty_name_no_fallback() {
assert!(get_name(Some(" ".to_string()), None, None).is_err());
}
#[test]
fn test_get_name_no_name_valid_path() {
assert_eq!(
get_name(None, None, Some(std::ffi::OsString::from("another-path"))).unwrap(),
"another-path"
);
}
#[test]
fn test_get_name_no_name_no_path_valid_url() {
assert_eq!(
get_name(
None,
Some("git@github.com:user/another-repo.git".to_string()),
None
)
.unwrap(),
"another-repo"
);
}
#[test]
fn test_get_name_all_none() {
assert!(get_name(None, None, None).is_err());
}
#[test]
fn test_name_from_url_standard() {
assert_eq!(
name_from_url("https://github.com/user/repo.git").unwrap(),
"repo"
);
assert_eq!(
name_from_url("https://github.com/user/repo").unwrap(),
"repo"
);
}
#[test]
fn test_name_from_url_trailing_slashes() {
assert_eq!(
name_from_url("https://github.com/user/repo/").unwrap(),
"repo"
);
assert_eq!(
name_from_url("https://github.com/user/repo///").unwrap(),
"repo"
);
}
#[test]
fn test_name_from_url_ssh_format() {
assert_eq!(
name_from_url("git@github.com:user/mylib.git").unwrap(),
"mylib"
);
}
#[test]
fn test_name_from_url_file_url() {
assert_eq!(name_from_url("file:///path/to/repo.git").unwrap(), "repo");
}
#[test]
fn test_name_from_url_simple_name() {
assert_eq!(name_from_url("repo").unwrap(), "repo");
assert_eq!(name_from_url("my-lib.git").unwrap(), "my-lib");
}
#[test]
fn test_name_from_url_empty() {
assert!(name_from_url("").is_err());
}
#[test]
fn test_name_from_osstring_simple() {
assert_eq!(
name_from_osstring(std::ffi::OsString::from("my-repo")).unwrap(),
"my-repo"
);
}
#[test]
fn test_name_from_osstring_path() {
let path = PathBuf::from_iter(["path", "to", "module"]);
assert_eq!(name_from_osstring(path.into_os_string()).unwrap(), "module");
}
#[test]
fn test_name_from_osstring_empty() {
assert!(name_from_osstring(std::ffi::OsString::from("")).is_err());
assert!(name_from_osstring(std::ffi::OsString::from(" ")).is_err());
}
#[test]
fn test_name_from_osstring_null_byte() {
assert!(name_from_osstring(std::ffi::OsString::from("foo\0bar")).is_err());
}
#[test]
fn test_osstring_to_string_valid() {
assert_eq!(
osstring_to_string(std::ffi::OsString::from("hello")).unwrap(),
"hello"
);
}
#[test]
fn test_path_to_string_valid() {
let path = std::path::Path::new("/home/user/repo");
assert_eq!(path_to_string(path).unwrap(), "/home/user/repo");
}
#[test]
fn test_path_to_os_string_roundtrip() {
let path = std::path::Path::new("/some/path");
let os = path_to_os_string(path);
assert_eq!(os, std::ffi::OsString::from("/some/path"));
}
#[test]
fn test_set_path_valid_utf8() {
let os = std::ffi::OsString::from("/valid/path");
assert_eq!(set_path(os).unwrap(), "/valid/path");
}
#[test]
fn test_get_sparse_paths_valid() {
let paths = Some(vec!["src/".to_string(), "docs/".to_string()]);
let result = get_sparse_paths(paths).unwrap();
assert_eq!(result, Some(vec!["src/".to_string(), "docs/".to_string()]));
}
#[test]
fn test_get_sparse_paths_none() {
let result = get_sparse_paths(None).unwrap();
assert!(result.is_none());
}
#[test]
fn test_get_sparse_paths_null_byte() {
let paths = Some(vec!["src/\0bad".to_string()]);
assert!(get_sparse_paths(paths).is_err());
}
#[test]
fn test_get_sparse_paths_empty_vec() {
let result = get_sparse_paths(Some(vec![])).unwrap();
assert_eq!(result, Some(vec![]));
}
}