use crate::*;
use std::fmt::{self, Debug, Formatter};
use std::ffi::OsStr;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::Arc;
#[derive(Clone)]
pub struct Repository {
dot_git: Arc<PathBuf>,
}
impl Repository {
pub fn from_bare_repository(dir: impl Into<PathBuf>) -> io::Result<Self> {
let dir = dir.into();
if dir.join(".git").exists() { return Err(io::Error::new(io::ErrorKind::InvalidData, "not a bare repository")); }
Ok(Self { dot_git: Arc::new(dir) })
}
pub fn from_regular_repository(dir: impl AsRef<Path>) -> io::Result<Self> {
let dir = dir.as_ref();
let dot_git = dir.join(".git");
if !dot_git.exists() { return Err(io::Error::new(io::ErrorKind::InvalidData, "not a regular repository")); }
Ok(Self { dot_git: Arc::new(dot_git) })
}
pub fn from_path(dir: impl AsRef<Path>) -> io::Result<Self> {
let dir = dir.as_ref();
let dot_git = dir.join(".git");
if dot_git.exists() {
Ok(Self { dot_git: Arc::new(dot_git) })
} else {
Self::from_bare_repository(dir)
}
}
pub fn local_branches(&self) -> io::Result<impl Iterator<Item = io::Result<Branch>>> {
let mut branches = Default::default();
gather_branches(OsStr::new(""), &self.dot_git.join("refs/heads"), &mut branches)?;
Ok(branches.into_iter().map(|(name, commit)| Ok(Branch { name, commit })))
}
pub fn remote_branches(&self) -> io::Result<impl Iterator<Item = io::Result<Branch>>> {
let mut branches = Default::default();
gather_branches(OsStr::new(""), &self.dot_git.join("refs/remotes"), &mut branches)?;
Ok(branches.into_iter().map(|(name, commit)| Ok(Branch { name, commit })))
}
pub fn cat_file_size(&self, hash: &blob::Hash) -> io::Result<u64> {
let hash = HashTempStr::new(hash);
let git = self.git().args(&["cat-file", "-s", hash.as_str()]).output()?;
match git.status.code() {
Some(0) => {},
Some(_) => return Err(io::Error::new(io::ErrorKind::Other, "git cat-file -s ... exited non-zero")),
None => return Err(io::Error::new(io::ErrorKind::Other, "git cat-file -s ... died by signal")),
}
Ok(String::from_utf8(git.stdout)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "git cat-file -s ... returned non-utf8 size"))?
.trim()
.parse()
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "git cat-file -s ... returned non-u64 file size"))?
)
}
pub fn cat_file_type(&self, hash: &unknown::Hash) -> io::Result<FileType> {
let hash = HashTempStr::new(hash);
let git = self.git().args(&["cat-file", "-t", hash.as_str()]).output()?;
match git.status.code() {
Some(0) => {},
Some(_) => return Err(io::Error::new(io::ErrorKind::Other, "git cat-file -t ... exited non-zero")),
None => return Err(io::Error::new(io::ErrorKind::Other, "git cat-file -t ... died by signal")),
}
Ok(String::from_utf8(git.stdout).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "git cat-file -t ... returned non-utf8 type"))?.trim().into())
}
pub fn cat_file_commit (&self, hash: &commit::Hash) -> io::Result<impl Read> { self.cat_file("commit", hash) }
pub fn cat_file_tree (&self, hash: &tree::Hash) -> io::Result<impl Read> { self.cat_file("tree", hash) }
pub fn cat_file_blob (&self, hash: &blob::Hash) -> io::Result<impl Read> { self.cat_file("blob", hash) }
fn git(&self) -> Command {
let mut c = Command::new("git");
c.current_dir(&*self.dot_git);
c
}
fn cat_file<T>(&self, ty: &str, hash: &generic::Hash<T>) -> io::Result<impl Read> {
let hash = HashTempStr::new(hash);
let mut git = self.git()
.args(&["cat-file", ty, hash.as_str()])
.stdin (Stdio::null())
.stderr(Stdio::null())
.stdout(Stdio::piped())
.spawn()?;
Ok(CatFileReader { stdout: git.stdout.take().unwrap(), child: git })
}
}
impl Debug for Repository {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
fmt.debug_struct("Repository")
.field("dot_git", &self.dot_git)
.field("local_branches", &self.local_branches().map(|b| b.collect::<Vec<_>>()))
.field("remote_branches", &self.remote_branches().map(|r| r.collect::<Vec<_>>()))
.finish()
}
}