mod tests;
use crate::errors::{io::IoError, DotbakError, Result};
use itertools::Itertools;
use std::{
fs,
path::{Path, PathBuf},
process::Output,
};
pub const REMOTE_NAME: &str = "origin";
pub const MAIN_BRANCH_NAME: &str = "main";
#[derive(Debug)]
pub struct Repository {
path: PathBuf,
}
impl Repository {
pub fn init<P>(path: P, remote_url: Option<String>) -> Result<Repository>
where
P: AsRef<Path>,
{
if !path.as_ref().exists() {
fs::create_dir_all(path.as_ref()).map_err(|err| IoError::Create {
source: err,
path: path.as_ref().to_path_buf(),
})?;
}
run_arbitrary_git_command(
path.as_ref(),
&["init", "--initial-branch", MAIN_BRANCH_NAME, "."],
)?;
let mut repo = Repository {
path: path.as_ref().to_path_buf(),
};
if let Some(url) = remote_url {
repo.set_remote(url)?;
}
Ok(repo)
}
pub fn load<P>(path: P) -> Result<Repository>
where
P: AsRef<Path>,
{
if !path.as_ref().exists() {
return Err(IoError::NotFound {
path: path.as_ref().to_path_buf(),
}
.into());
}
if !path.as_ref().join(".git").exists() {
return Err(IoError::NotFound {
path: path.as_ref().to_path_buf(),
}
.into());
}
Ok(Repository {
path: path.as_ref().to_path_buf(),
})
}
pub fn clone<P, S>(path: P, url: S) -> Result<Repository>
where
P: AsRef<Path>,
S: ToString,
{
let path = path.as_ref();
let url = url.to_string();
if !path.exists() {
fs::create_dir_all(path).map_err(|err| IoError::Create {
source: err,
path: path.to_path_buf(),
})?;
}
run_arbitrary_git_command(path, &["clone", &url, "."])?;
let repo = Repository {
path: path.to_path_buf(),
};
Ok(repo)
}
pub fn arbitrary_command(&mut self, args: &[&str]) -> Result<Output> {
run_arbitrary_git_command(&self.path, args)
}
pub fn set_remote<S>(&mut self, url: S) -> Result<Output>
where
S: ToString,
{
let url = url.to_string();
let result = self.arbitrary_command(&["remote", "set-url", REMOTE_NAME, &url]);
match result {
Ok(output) => Ok(output),
Err(DotbakError::Io(IoError::CommandRun { stderr, .. }))
if stderr == *"error: No such remote 'origin'\n" =>
{
self.arbitrary_command(&["remote", "add", REMOTE_NAME, &url])?;
self.arbitrary_command(&["remote", "set-url", REMOTE_NAME, &url])
}
Err(e) => Err(e),
}
}
pub fn commit(&mut self, message: &str) -> Result<[Output; 2]> {
Ok([
self.arbitrary_command(&["add", "."])?,
self.arbitrary_command(&["commit", "-am", message])?,
])
}
pub fn push(&mut self) -> Result<Output> {
self.arbitrary_command(&["push", REMOTE_NAME, MAIN_BRANCH_NAME])
}
pub fn pull(&mut self) -> Result<Output> {
self.arbitrary_command(&["pull", REMOTE_NAME, MAIN_BRANCH_NAME])
}
pub fn delete(self) -> Result<()> {
fs::remove_dir_all(&self.path).map_err(|err| IoError::Delete {
source: err,
path: self.path,
})?;
Ok(())
}
}
#[cfg(test)]
impl Repository {
pub fn path(&self) -> &Path {
&self.path
}
}
fn run_arbitrary_git_command<P>(path: P, args: &[&str]) -> Result<Output>
where
P: AsRef<Path>,
{
let output = std::process::Command::new("git")
.args(args)
.current_dir(path)
.output()
.map_err(|err| IoError::CommandIO {
source: err,
command: "git".to_string(),
args: args.iter().map(|s| s.to_string()).collect_vec(),
})?;
if output.status.success() {
return Ok(output);
}
let string_stdout = String::from_utf8_lossy(&output.stdout).to_string();
let string_stderr = String::from_utf8_lossy(&output.stderr).to_string();
match string_stdout {
_ if string_stdout.contains("nothing to commit") => Ok(output),
_ => Err(IoError::CommandRun {
command: "git".to_string(),
args: args.iter().map(|s| s.to_string()).collect_vec(),
stdout: string_stdout,
stderr: string_stderr,
}
.into()),
}
}