#![allow(dead_code, unused_imports)]
use std::error::Error;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use dialoguer::{Input, Password};
use git2::{Commit, ObjectType, PushOptions, Remote, Repository};
pub fn get_repo(repo_path: &str) -> Result<Repository, git2::Error> {
let repo_root = PathBuf::from(repo_path);
log::debug!("Checking repo on {}", repo_root.display());
Repository::open(repo_root.as_os_str())
}
pub fn get_last_commit(repo: &Repository) -> Result<Commit<'_>, git2::Error> {
let obj = repo.head()?.resolve()?.peel(ObjectType::Commit)?;
obj.into_commit()
.map_err(|_| git2::Error::from_str("Couldn't find commit"))
}
pub fn untracked_changed_local_files(
repo: &Repository,
) -> Result<bool, Box<dyn std::error::Error>> {
let mut index = repo.index().unwrap();
log::debug!("Checking git index...");
match index.add_all(
["."],
git2::IndexAddOption::DEFAULT,
Some(&mut |path: &Path, _matched_spec: &[u8]| -> i32 {
let status = repo.status_file(path).unwrap();
if status.contains(git2::Status::WT_MODIFIED) || status.contains(git2::Status::WT_NEW) {
log::debug!(
"File not included in git index. Aborting process.\
Please run 'git status' to get list of file to work on"
);
-1
} else {
0
}
}),
) {
Ok(()) => Ok(true),
Err(_) => Ok(false),
}
}
pub fn add_all(repo: &Repository) {
let mut index = repo.index().unwrap();
log::debug!("Running 'git add'");
index
.add_all(
["."],
git2::IndexAddOption::DEFAULT,
Some(&mut |path: &Path, _matched_spec: &[u8]| -> i32 {
let status = repo.status_file(path).unwrap();
if status.contains(git2::Status::WT_MODIFIED)
|| status.contains(git2::Status::WT_NEW)
{
log::debug!(
" - Adding file: '{}' with status {:?}",
path.display(),
status
);
0
} else {
log::debug!(
" - NOT adding file: '{}' with status {:?}",
path.display(),
status
);
1
}
}),
)
.unwrap();
index.write().unwrap();
}
pub fn commit(repo: &Repository) {
let mut index = repo.index().unwrap();
let oid = index.write_tree().unwrap();
let signature = repo.signature().unwrap();
let parent_commit = repo.head().unwrap().peel_to_commit().unwrap();
let tree = repo.find_tree(oid).unwrap();
repo.commit(
Some("HEAD"),
&signature,
&signature,
"testing git2-rs... commit created programatically...",
&tree,
&[&parent_commit],
)
.unwrap();
}
pub fn push(mut remote: Remote) -> Result<(), git2::Error> {
let mut callbacks = git2::RemoteCallbacks::new();
callbacks.credentials(|_url, username_from_url, _allowed_types| {
log::debug!("url is: {}", _url);
log::debug!(
"username from url is: {}",
username_from_url.unwrap_or("Not defined")
); log::debug!("allowed types are: {:#?}", _allowed_types);
if username_from_url.unwrap().eq("git") {
git2::Cred::ssh_key(
username_from_url.unwrap(),
None,
std::path::Path::new(&format!(
"{}/.ssh/gitlab_vcef",
std::env::var("HOME").expect("key file ~/.ssh/gitlab_vcef not found")
)),
None,
)
} else {
let username: String = Input::new()
.with_prompt("Username: ")
.interact_text()
.unwrap();
let password = Password::new()
.with_prompt("New Password")
.with_confirmation("Confirm password", "Passwords mismatching")
.interact()
.unwrap();
git2::Cred::userpass_plaintext(&username, &password) }
});
callbacks.push_update_reference(|_reference_name, callback_status| {
log::debug!("reference name: {}", _reference_name);
log::debug!(
"callback status: {}",
callback_status.unwrap_or("Not defined")
);
Ok(())
});
let po = &mut PushOptions::default();
po.remote_callbacks(callbacks);
remote.push(
&[
"+refs/heads/main",
"+refs/heads/apply-dynamic-target-session",
],
Some(po),
)
}
pub fn fetch<'a>(
repo: &'a git2::Repository,
refs: &[&str],
remote: &'a mut git2::Remote,
) -> Result<git2::AnnotatedCommit<'a>, Box<dyn Error>> {
let mut cb = git2::RemoteCallbacks::new();
cb.credentials(|_url, username_from_url, _allowed_types| {
log::debug!("url is: {}", _url);
log::debug!(
"username from url is: {}",
username_from_url.unwrap_or("Not defined")
); log::debug!("allowed types are: {:#?}", _allowed_types);
if username_from_url.unwrap().eq("git") {
git2::Cred::ssh_key(
username_from_url.unwrap(),
None,
std::path::Path::new(&format!(
"{}/.ssh/gitlab_vcef",
std::env::var("HOME").expect("key file ~/.ssh/gitlab_vcef not found")
)),
None,
)
} else {
let username: String = Input::new()
.with_prompt("Username: ")
.interact_text()
.unwrap();
let password = Password::new()
.with_prompt("New Password")
.with_confirmation("Confirm password", "Passwords mismatching")
.interact()
.unwrap();
git2::Cred::userpass_plaintext(&username, &password) }
});
cb.transfer_progress(|stats| {
if stats.received_objects() == stats.total_objects() {
print!(
"Resolving deltas {}/{}.\r",
stats.indexed_deltas(),
stats.total_deltas()
);
} else if stats.total_objects() > 0 {
print!(
"Received {}/{} objects ({}) in {} bytes.\r",
stats.received_objects(),
stats.total_objects(),
stats.indexed_objects(),
stats.received_bytes()
);
}
io::stdout().flush().unwrap();
true
});
let mut fo = git2::FetchOptions::new();
fo.remote_callbacks(cb);
fo.download_tags(git2::AutotagOption::All);
log::info!("Fetching {} for repo", remote.name().unwrap());
remote.fetch(refs, Some(&mut fo), None)?;
let stats = remote.stats();
if stats.local_objects() > 0 {
println!(
"\rReceived {}/{} objects in {} bytes (used {} local \
objects)",
stats.indexed_objects(),
stats.total_objects(),
stats.received_bytes(),
stats.local_objects()
);
} else {
println!(
"\rReceived {}/{} objects in {} bytes",
stats.indexed_objects(),
stats.total_objects(),
stats.received_bytes()
);
}
let fetch_head = repo.find_reference("FETCH_HEAD")?;
Ok(repo.reference_to_annotated_commit(&fetch_head)?)
}
pub fn has_conflicts(
repo: &Repository,
local: &git2::AnnotatedCommit,
remote: &git2::AnnotatedCommit,
) -> Result<(), Box<dyn 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 idx = repo.merge_trees(&ancestor, &local_tree, &remote_tree, None)?;
if idx.has_conflicts() {
println!("Merge conficts detected...");
return Err("Conflicts have been found while checking local and remote repos. Please fix conflicts and try again, Your local repo is instact.".into());
}
Ok(())
}
pub fn fetch_and_check_conflicts(repo: &Repository) -> core::result::Result<(), Box<dyn Error>> {
let head_commit = repo.reference_to_annotated_commit(&repo.head()?)?;
let mut remote_aux = repo.find_remote("origin")?;
let remote_branch = "apply-dynamic-target-session";
let fetch_commit = fetch(repo, &[remote_branch], &mut remote_aux)?;
has_conflicts(repo, &head_commit, &fetch_commit)?;
Ok(())
}