use crate::procop;
use directories::ProjectDirs;
use git2::{ObjectType, Repository};
use std::error;
use std::fmt::Display;
use std::fs::File;
use std::io::prelude::*;
pub fn update_repository(url: &str) -> Result<Repository, Box<dyn error::Error>> {
let mut target_path = ProjectDirs::from(
procop::PROCOP_QUALIFIER,
procop::PROCOP_ORGANIZATION,
procop::PROCOP_APPLICATION,
)
.expect(
"\
No valid home directory path could be retrieved from the operating system. \
This program wants to store its application data there.",
)
.data_dir()
.to_path_buf();
target_path.push(git_url_to_filename(url)?);
match target_path.exists() {
true => Ok(Repository::open(target_path)?),
false => {
let repo = Repository::clone(url, target_path)?;
Ok(repo)
}
}
}
pub fn clone_file_from_repository(
url: &str,
file_path: &str,
target_path: &str,
) -> std::io::Result<()> {
let repository = Repository::clone(url, target_path).unwrap();
let oid = repository
.revparse_single(&format!("HEAD:{}", file_path))
.unwrap()
.id();
let object = repository.find_object(oid, Some(ObjectType::Blob)).unwrap();
let file_content = object.as_blob().unwrap().content();
let mut file = File::create(file_path)?;
file.write_all(file_content).unwrap();
Ok(())
}
fn git_url_to_filename(url: &str) -> Result<String, InvalidGitUrl> {
Ok(sanitise_file_name::sanitise(&extract_git_repo_name(url)?))
}
#[derive(Debug, PartialEq)]
struct InvalidGitUrl(&'static str);
impl Display for InvalidGitUrl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl error::Error for InvalidGitUrl {}
fn extract_git_repo_name(url: &str) -> Result<String, InvalidGitUrl> {
if !url.contains(".git") {
return Err(InvalidGitUrl("No '.git' found in URL."));
}
let mut target = url[0..url.find(".git").expect(
"\
Unexpected error. \
We checked the existence of '.git' within the git Git URL before, \
nevertheless it seems not be present.",
)]
.to_string();
["@", ":///", "://"].iter().for_each(|sep| {
if let Some(index) = target.find(sep) {
target.drain(..index + sep.len());
}
});
Ok(target)
}
#[cfg(test)]
mod tests {
use crate::git::git_url_to_filename;
use super::{extract_git_repo_name, update_repository};
#[test]
fn invalid_git_urls() {
let result = extract_git_repo_name("foogit");
assert!(result.is_err());
assert_eq!(result.unwrap_err().0, "No '.git' found in URL.");
}
#[test]
fn parse_git_urls() {
assert_eq!(
extract_git_repo_name("ssh://user@host.xz:port/path/to/repo.git/path/to/file.txt"),
Ok("host.xz:port/path/to/repo".to_string())
);
assert_eq!(
extract_git_repo_name("ssh://user@host.xz:port/path/to/repo.git"),
Ok("host.xz:port/path/to/repo".to_string())
);
assert_eq!(
extract_git_repo_name("ssh://host.xz:port/path/to/repo.git/path/to/file.txt"),
Ok("host.xz:port/path/to/repo".to_string())
);
assert_eq!(
extract_git_repo_name("user@host.xz:/path/to/repo.git/path/to/file"),
Ok("host.xz:/path/to/repo".to_string())
);
let git_base_url = "host.xy/path/to/repo";
["rsync://", "git://", "http://", "https://"]
.iter()
.for_each(|protocol| {
assert_eq!(
extract_git_repo_name(&format!("{protocol}{git_base_url}.git")),
Ok(git_base_url.to_string())
)
});
assert_eq!(
extract_git_repo_name("file:///path/to/repo.git"),
Ok("path/to/repo".to_string())
);
assert_eq!(
extract_git_repo_name("host.xz:/path/to/repo.git"),
Ok("host.xz:/path/to/repo".to_string())
);
}
#[test]
fn sanitized_url() {
assert_eq!(
git_url_to_filename("ssh://user@host.xz:port/path/to/repo.git/path/to/file.txt")
.unwrap(),
"host.xz_port_path_to_repo"
);
assert_eq!(
git_url_to_filename("ssh://user@host.xz/~user/path/to/repo.git/").unwrap(),
"host.xz_~user_path_to_repo"
);
}
#[test]
#[ignore]
fn get_repository() {
assert!(!std::path::Path::new("/root/.local/share/procop/test/README.md").exists());
let repo1 = update_repository("/test.git").expect("Expected not to fail.");
assert!(std::path::Path::new("/root/.local/share/procop/test/README.md").exists());
assert!(std::path::Path::new("/root/.local/share/procop/test/README.md").is_file());
assert!(repo1.tag_names(None).unwrap().is_empty());
let _ = repo1.tag_lightweight(
"foo",
repo1.head().unwrap().peel_to_commit().unwrap().as_object(),
false,
);
assert_eq!(
repo1
.tag_names(None)
.expect("Expected a list of Git tags.")
.get(0)
.expect("Expected the first Git tag 'foo' exists."),
"foo"
);
let repo2 = update_repository("/test.git").expect("Expected not to fail.");
assert_eq!(
repo2
.tag_names(None)
.expect("Expected a list of Git tags.")
.get(0)
.expect("Expected the first Git tag 'foo' exists."),
"foo"
);
let _ = std::fs::remove_dir_all("/root/.local/share/procop/test")
.expect("Expected directory '/root/.local/share/procop/test' exists.");
assert!(!std::path::Path::new("/root/.local/share/procop/test/README.md").exists());
let repo3 = update_repository("/test.git").expect("Expected not to fail.");
assert!(repo3
.tag_names(None)
.expect("Expected an (empty) list of Git tags.")
.is_empty());
}
}