use std::path::Path;
use anyhow::Result;
pub struct GitSidecar {
repo: git2::Repository,
}
#[derive(Debug, Clone, Default)]
pub struct GitStatus {
pub branch: String,
pub modified: usize,
pub staged: usize,
pub untracked: usize,
pub ahead: usize,
pub behind: usize,
}
impl GitSidecar {
pub fn open(path: &Path) -> Result<Self> {
let repo = if path.join(".git").exists() {
git2::Repository::open(path)?
} else {
git2::Repository::init(path)?
};
Ok(Self { repo })
}
pub fn status(&self) -> Result<GitStatus> {
let branch = self
.repo
.head()
.ok()
.and_then(|h| h.shorthand().map(|s| s.to_string()))
.unwrap_or_else(|| "HEAD".to_string());
let statuses = self.repo.statuses(None)?;
let mut modified = 0usize;
let mut staged = 0usize;
let mut untracked = 0usize;
for entry in statuses.iter() {
let s = entry.status();
if s.contains(git2::Status::CURRENT) { continue; }
if s.intersects(git2::Status::INDEX_MODIFIED | git2::Status::INDEX_NEW) {
staged += 1;
}
if s.intersects(git2::Status::WT_MODIFIED | git2::Status::WT_DELETED) {
modified += 1;
}
if s.contains(git2::Status::WT_NEW) {
untracked += 1;
}
}
let (ahead, behind) = self.count_ahead_behind()?;
Ok(GitStatus { branch, modified, staged, untracked, ahead, behind })
}
fn count_ahead_behind(&self) -> Result<(usize, usize)> {
let head = self.repo.head().ok().and_then(|h| h.target());
let branch = self.repo.head().ok().and_then(|h| h.shorthand().map(|s| s.to_string()));
let upstream = branch.as_ref().and_then(|b| {
self.repo.find_branch(b, git2::BranchType::Local).ok()
.and_then(|b| b.upstream().ok())
.and_then(|u| u.get().target())
});
match (head, upstream) {
(Some(h), Some(u)) => {
let (a, b) = self.repo.graph_ahead_behind(h, u)?;
Ok((a as usize, b as usize))
}
_ => Ok((0, 0)),
}
}
pub fn stage(&self, paths: &[&Path]) -> Result<()> {
let mut index = self.repo.index()?;
for path in paths {
index.add_path(path)?;
}
index.write()?;
Ok(())
}
pub fn stage_all(&self) -> Result<()> {
let mut index = self.repo.index()?;
index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)?;
index.write()?;
Ok(())
}
pub fn unstage_all(&self) -> Result<()> {
let head = self.repo.head()?;
let tree = head.peel_to_tree()?;
let mut index = self.repo.index()?;
index.read_tree(&tree)?;
index.write()?;
Ok(())
}
pub fn commit(&self, message: &str) -> Result<String> {
let mut index = self.repo.index()?;
let tree_oid = index.write_tree()?;
let tree = self.repo.find_tree(tree_oid)?;
let parent_commit = self.repo.head().ok().and_then(|h| h.peel_to_commit().ok());
let parents: Vec<&git2::Commit> = parent_commit.iter().collect();
let signature = git2::Signature::now("TelaRex User", "user@telarex")?;
let oid = self.repo.commit(
Some("HEAD"),
&signature,
&signature,
message,
&tree,
&parents,
)?;
Ok(oid.to_string())
}
pub fn push(&self, remote: &str, branch: &str) -> Result<()> {
let mut remote = self.repo.find_remote(remote)?;
let refspec = format!("refs/heads/{}:refs/heads/{}", branch, branch);
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.push_update_reference(|_refname, status| {
if let Some(msg) = status {
Err(git2::Error::from_str(msg))
} else {
Ok(())
}
});
let mut opts = git2::PushOptions::new();
opts.remote_callbacks(callbacks);
remote.push(&[&refspec], Some(&mut opts))?;
Ok(())
}
pub fn fetch(&self, remote: &str) -> Result<()> {
let mut remote = self.repo.find_remote(remote)?;
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.transfer_progress(|_| true);
let mut opts = git2::FetchOptions::new();
opts.remote_callbacks(callbacks);
remote.fetch(&["refs/heads/*:refs/heads/*"], Some(&mut opts), None)?;
Ok(())
}
pub fn log(&self, max_count: usize) -> Result<Vec<GitCommit>> {
let mut revwalk = self.repo.revwalk()?;
revwalk.push_head()?;
revwalk.set_sorting(git2::Sort::TIME)?;
let mut commits = Vec::new();
for oid in revwalk.take(max_count) {
if let Ok(oid) = oid {
if let Ok(commit) = self.repo.find_commit(oid) {
commits.push(GitCommit {
oid: oid.to_string(),
message: commit.message().unwrap_or("").to_string(),
author: commit.author().name().unwrap_or("unknown").to_string(),
time: commit.time().seconds(),
});
}
}
}
Ok(commits)
}
}
#[derive(Debug, Clone)]
pub struct GitCommit {
pub oid: String,
pub message: String,
pub author: String,
pub time: i64,
}