use std::env;
use std::path::Path;
use git2::BranchType;
use crate::git_util;
fn fetch<'a>(
repo: &'a git2::Repository,
refs: &[&str],
remote: &'a mut git2::Remote,
) -> Result<git2::AnnotatedCommit<'a>, git2::Error> {
let mut fo = git2::FetchOptions::new();
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.credentials(|_url, username_from_url, _allowed_types| {
git_util::get_ssh_key(&username_from_url)
});
fo.remote_callbacks(callbacks);
fo.download_tags(git2::AutotagOption::All);
remote.fetch(refs, Some(&mut fo), None)?;
let fetch_head = repo.find_reference("FETCH_HEAD")?;
Ok(repo.reference_to_annotated_commit(&fetch_head)?)
}
fn normal_merge(
repo: &git2::Repository,
local: &git2::AnnotatedCommit,
remote: &git2::AnnotatedCommit,
) -> Result<(), git2::Error> {
let local_tree = repo.find_commit(local.id())?.tree()?;
let remote_tree = repo.find_commit(remote.id())?.tree()?;
let ancestor = repo
.find_commit(repo.merge_base(local.id(), remote.id())?)?
.tree()?;
let mut idx = repo.merge_trees(&ancestor, &local_tree, &remote_tree, None)?;
if idx.has_conflicts() {
repo.checkout_index(Some(&mut idx), None)?;
return Ok(());
}
let result_tree = repo.find_tree(idx.write_tree_to(repo)?)?;
let msg = format!("Merge: {} into {}", remote.id(), local.id());
let sig = repo.signature()?;
let local_commit = repo.find_commit(local.id())?;
let remote_commit = repo.find_commit(remote.id())?;
let _merge_commit = repo.commit(
Some("HEAD"),
&sig,
&sig,
&msg,
&result_tree,
&[&local_commit, &remote_commit],
)?;
repo.checkout_head(None)?;
Ok(())
}
fn fast_forward(
repo: &git2::Repository,
lb: &mut git2::Reference,
rc: &git2::AnnotatedCommit,
) -> Result<(), git2::Error> {
let name = match lb.name() {
Some(s) => s.to_string(),
None => String::from_utf8_lossy(lb.name_bytes()).to_string(),
};
let msg = format!("Fast-Forward: Setting {} to id: {}", name, rc.id());
lb.set_target(rc.id(), &msg)?;
repo.set_head(&name)?;
repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))?;
Ok(())
}
fn do_merge<'a>(
repo: &'a git2::Repository,
remote_branch: &str,
fetch_commit: git2::AnnotatedCommit<'a>,
) -> Result<(), git2::Error> {
let analysis = repo.merge_analysis(&[&fetch_commit])?;
if analysis.0.is_fast_forward() {
let refname = format!("refs/heads/{}", remote_branch);
match repo.find_reference(&refname) {
Ok(mut r) => {
fast_forward(repo, &mut r, &fetch_commit)?;
}
Err(_) => {
repo.reference(
&refname,
fetch_commit.id(),
true,
&format!("Setting {} to {}", remote_branch, fetch_commit.id()),
)?;
repo.set_head(&refname)?;
repo.checkout_head(Some(
git2::build::CheckoutBuilder::default()
.allow_conflicts(true)
.conflict_style_merge(true)
.force(),
))?;
}
};
} else if analysis.0.is_normal() {
let head_commit = repo.reference_to_annotated_commit(&repo.head()?)?;
normal_merge(&repo, &head_commit, &fetch_commit)?;
}
Ok(())
}
pub fn pull(
repo: &git2::Repository,
remote_name: &str,
remote_branch: &str,
) -> Result<(), git2::Error> {
let mut remote = repo.find_remote(remote_name)?;
let fetch_commit = fetch(&repo, &[remote_branch], &mut remote)?;
do_merge(repo, remote_branch, fetch_commit)?;
Ok(())
}
pub fn get_ssh_key(username: &Option<&str>) -> Result<git2::Cred, git2::Error> {
git2::Cred::ssh_key(
username.unwrap(),
None,
Path::new(&format!("{}/.ssh/id_rsa", env::var("HOME").unwrap())),
None,
)
}
pub fn commit_all_changes(repo: &git2::Repository, commit_msg: &str) -> anyhow::Result<()> {
let mut index = repo.index()?;
index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)?;
let oid = index.write_tree()?;
let tree = repo.find_tree(oid)?;
let sig = repo.signature()?;
let commit = repo
.head()
.map(|r| r.target().unwrap())
.and_then(|oid| repo.find_commit(oid))
.ok();
match commit {
Some(c) => repo.commit(Some("HEAD"), &sig, &sig, commit_msg, &tree, &[&c])?,
None => repo.commit(Some("HEAD"), &sig, &sig, commit_msg, &tree, &[])?,
};
Ok(())
}
pub fn get_active_branch(repo: &git2::Repository) -> anyhow::Result<Option<String>> {
let branches = repo.branches(Some(BranchType::Local))?;
for branch in branches {
let (branch, _) = branch.unwrap();
if branch.is_head() {
return Ok(branch.name().unwrap().map(String::from));
}
}
Ok(None)
}