#![allow(unused_imports, unused_variables, dead_code)]
#[macro_use]
extern crate failure;
use failure::{Fail, ResultExt};
use std::convert::TryFrom;
use std::fmt;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{self, Command, Stdio, Output};
mod status;
pub mod commands;
pub use status::Status;
type Result<T> = std::result::Result<T, failure::Error>;
#[derive(Fail, Debug)]
pub struct GitError {
cmd: String,
code: Option<i32>,
#[cause] stderr: failure::Error,
}
impl fmt::Display for GitError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(code) = self.code {
write!(f, "{} returned error code {}", self.cmd, code)
} else {
write!(f, "{} was stopped...", self.cmd)
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum GitOut {
Print,
Pipe,
}
impl Default for GitOut {
fn default() -> Self {
GitOut::Pipe
}
}
#[derive(Debug, Default, PartialEq, Eq, Hash)]
pub struct Repository {
location: Option<PathBuf>,
stdout: GitOut,
}
pub trait CommandOptions {
type Output;
fn git_args(&self) -> Vec<&str>;
fn parse_output(&self, out: &str) -> Result<Self::Output>;
fn run(&self, repo: &Repository) -> Result<Self::Output> {
let args = self.git_args();
let out = repo.run(args)?;
self.parse_output(&out)
}
}
impl Repository {
pub fn new() -> Repository {
Repository {
..Default::default()
}
}
pub fn at<P: AsRef<Path>>(path: P) -> Result<Repository> {
Ok(Repository {
location: Some(
fs::canonicalize(path).context("failed to canonicalize repository path")?,
),
..Default::default()
})
}
pub fn is_init(&self) -> bool {
let git_dir = match &self.location {
Some(loc) => loc.join(".git"),
None => PathBuf::from("./.git"),
};
git_dir.exists() && git_dir.is_dir()
}
pub fn gitout(&mut self, val: GitOut) -> &mut Repository {
self.stdout = val;
self
}
pub fn add(&mut self) -> Result<&mut Self> {
let args = vec!["add", "--all"];
self.run(args)?;
Ok(self)
}
pub fn commit(&mut self, msg: &str) -> Result<&mut Self> {
let args = vec!["commit", "-m", msg, "-q", "--allow-empty"];
self.run(args)?;
Ok(self)
}
pub fn fetch(&mut self) -> Result<&mut Self> {
let args = vec!["fetch", "--all", "-q"];
self.run(args)?;
Ok(self)
}
pub fn init(&mut self) -> Result<&mut Self> {
if let Some(loc) = &self.location {
if !loc.exists() {
fs::create_dir_all(loc)?;
}
}
let args = vec!["init", "-q"];
self.run(args)?;
Ok(self)
}
pub fn notes(&mut self, msg: &str) -> Result<&mut Self> {
let args = vec!["notes", "add", "-m", msg];
self.run(args)?;
Ok(self)
}
pub fn pull(&mut self) -> Result<&mut Self> {
let args = vec!["pull", "-q"];
self.run(args)?;
Ok(self)
}
pub fn push(&mut self) -> Result<&mut Self> {
let args = vec!["push", "-q"];
self.run(args)?;
Ok(self)
}
pub fn remote(&mut self, name: &str, url: &str) -> Result<&mut Self> {
let args = vec!["remote", "add", name, url];
self.run(args)?;
Ok(self)
}
pub fn status(&self) -> Result<Status> {
let args = vec!["status", "--porcelain=v2", "--branch", "--ignored"];
let out = self.run(args)?;
Status::try_from(out.as_str())
}
pub fn stash(&mut self) -> Result<&mut Self> {
let args = vec!["stash", "-q"];
self.run(args)?;
Ok(self)
}
pub fn tag(&mut self, tagname: &str) -> Result<&mut Self> {
let args = vec!["tag", tagname];
self.run(args)?;
Ok(self)
}
fn run(&self, args: Vec<&str>) -> Result<String> {
let mut cmd = Command::new("git");
cmd.stdin(Stdio::inherit());
if matches!(self.stdout, GitOut::Print) {
cmd.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
}
if let Some(path) = &self.location {
(&mut cmd).current_dir(path);
}
(&mut cmd).args(&args);
if matches!(self.stdout, GitOut::Print) {
let status = cmd.status().context("git execution failed")?;
if status.success() {
return Ok(String::new())
} else {
Err(GitError {
cmd: format!("git {}", args[0]),
code: status.code(),
stderr: format_err!("check stderr output"),
}.into())
}
} else {
let out = cmd.output().context("git execution failed")?;
if out.status.success() {
Ok(String::from_utf8(out.stdout)?)
} else {
Err(GitError {
cmd: format!("git {}", args[0]),
code: out.status.code(),
stderr: format_err!("{}", std::str::from_utf8(&out.stderr)?),
}.into())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mhgit_unit() {
assert_eq!(2 + 2, 4);
}
}