use colored::*;
use git2::{Config, ErrorCode, Repository, Signature, StatusOptions};
use std::io::{self, stdout, Write};
use std::path::Path;
use std::process::{Command, Stdio};
fn stage(repo: &Repository) -> Result<Vec<(String, git2::Status)>, git2::Error> {
let mut index = repo.index()?;
let mut options = StatusOptions::new();
options.include_untracked(true).recurse_untracked_dirs(true);
let mut files: Vec<(String, git2::Status)> = Vec::new();
for entry in repo.statuses(Some(&mut options))?.iter() {
let path = Path::new(std::str::from_utf8(entry.path_bytes()).unwrap());
match entry.status() {
status if status.intersects(git2::Status::INDEX_NEW | git2::Status::WT_NEW) => {
files.push((path.display().to_string(), git2::Status::INDEX_NEW));
index.add_path(&path)?;
}
status
if status.intersects(git2::Status::INDEX_MODIFIED | git2::Status::WT_MODIFIED) =>
{
files.push((path.display().to_string(), git2::Status::INDEX_MODIFIED));
index.add_path(&path)?;
}
status if status.intersects(git2::Status::INDEX_DELETED | git2::Status::WT_DELETED) => {
files.push((path.display().to_string(), git2::Status::INDEX_DELETED));
index.remove_path(&path)?;
}
_ => continue,
}
}
index.write()?;
Ok(files)
}
fn commit(repo: &Repository, message: &str) -> Result<(), git2::Error> {
let mut index = repo.index()?;
let tree_oid = index.write_tree()?;
let tree = repo.find_tree(tree_oid)?;
let config = Config::open_default()?;
let name = config.get_string("user.name")?;
let email = config.get_string("user.email")?;
let signature = Signature::now(&name, &email)?;
let head = repo.head();
let head = match head {
Ok(head) => head,
Err(ref e) if e.code() == ErrorCode::UnbornBranch => {
repo.commit(Some("HEAD"), &signature, &signature, message, &tree, &[])?;
return Ok(());
}
Err(e) => return Err(e),
};
let head_commit = repo.find_commit(head.target().unwrap())?;
repo.commit(
Some("HEAD"),
&signature,
&signature,
message,
&tree,
&[&head_commit],
)?;
Ok(())
}
fn lines(repo: &Repository) -> Result<(usize, usize), git2::Error> {
let mut index = repo.index()?;
let oid = index.write_tree()?;
let tree = repo.find_tree(oid)?;
let head_commit = repo.head()?.peel_to_commit()?;
let head_tree = head_commit.tree()?;
let diff = repo.diff_tree_to_tree(Some(&head_tree), Some(&tree), None)?;
Ok((diff.stats()?.insertions(), diff.stats()?.deletions()))
}
fn main() {
let repo = Repository::discover(".").unwrap_or_else(|_| {
eprintln!("{}", "Error opening git repo •◠•".red());
std::process::exit(1);
});
println!(
"{}",
repo.path()
.parent()
.and_then(|path| path.file_name())
.and_then(|name| name.to_str())
.unwrap_or("no name")
.italic()
.cyan()
);
let files = stage(&repo).unwrap_or_else(|_| {
eprintln!("{}", "Error staging files •◠•".red());
std::process::exit(1);
});
if files.len() == 0 {
println!("{}", "No changes to commit •◡•".yellow());
std::process::exit(0);
}
for (path, status) in &files {
let print_path = path;
match status {
&git2::Status::INDEX_NEW => {
print!("{}", ("+ ".to_owned() + &print_path).green())
}
&git2::Status::INDEX_MODIFIED => {
print!("{}", ("M ".to_owned() + &print_path).yellow())
}
&git2::Status::INDEX_DELETED => {
print!("{}", ("- ".to_owned() + &print_path).red())
}
_ => continue,
}
println!();
}
let (lines_inserted, lines_deleted) = lines(&repo).unwrap_or_else(|_| {
eprintln!("{}", "Error reading git info •◠•".red());
std::process::exit(1);
});
println!(
"\n{} files staged, {} lines added, {} lines deleted",
files.len().to_string().yellow(),
("+".to_owned() + &lines_inserted.to_string()).green(),
("-".to_owned() + &lines_deleted.to_string()).red(),
);
print!("{}", ": ".cyan());
stdout().flush().unwrap();
let mut commit_title = String::new();
io::stdin()
.read_line(&mut commit_title)
.expect("Failed to read input");
let commit_title = commit_title.trim();
commit(&repo, commit_title).unwrap_or_else(|_| {
eprintln!("{}", "Error committing changes •◠•".red());
std::process::exit(1);
});
if !Command::new("git")
.arg("push")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.status()
.unwrap_or_else(|_| {
eprintln!("{}", "Unable to call 'git push' •◠•".red());
std::process::exit(1);
})
.success()
{
eprintln!("{}", "Error pushing code •◠•".red());
} else {
println!("{}", "Success •◡•".green());
}
}