use git2::{FetchOptions, RemoteCallbacks, Repository};
use git2_credentials::CredentialHandler;
use log::info;
use std::path::PathBuf;
use tempfile::tempdir;
pub fn remote_exists(remote: &str) -> bool {
let temp_dir = match tempdir() {
Ok(dir) => dir,
Err(e) => {
log::error!("Failed to create temporary directory: {}", e);
return false;
}
};
let repo = match Repository::init(temp_dir.path()) {
Ok(repo) => repo,
Err(e) => {
log::error!("Failed to initialize repository: {}", e);
return false;
}
};
let mut fetch_options = FetchOptions::new();
if remote.starts_with("git@") {
let mut callbacks = RemoteCallbacks::new();
let git_config = match git2::Config::open_default() {
Ok(config) => config,
Err(e) => {
log::error!("Failed to open git config: {}", e);
return false;
}
};
let mut handler = CredentialHandler::new(git_config);
callbacks.credentials(move |url, username, allowed| {
handler.try_next_credential(url, username, allowed)
});
fetch_options.remote_callbacks(callbacks);
}
let mut remote = match repo.remote_anonymous(remote) {
Ok(remote) => remote,
Err(e) => {
log::debug!("Failed to create remote: {}", e);
return false;
}
};
match remote.fetch(
&["refs/heads/*:refs/heads/*"],
Some(&mut fetch_options),
None,
) {
Ok(_) => true, Err(e) => {
if e.code() == git2::ErrorCode::NotFound {
false
} else {
log::debug!("Unexpected error while checking remote: {}", e);
false
}
}
}
}
pub fn git_clone(remote: &str, path: &str) -> PathBuf {
let mut callbacks = RemoteCallbacks::new();
let git_config = git2::Config::open_default().unwrap();
let mut handler = CredentialHandler::new(git_config);
callbacks.credentials(move |url, username, allowed| {
handler.try_next_credential(url, username, allowed)
});
let mut fetch_options = FetchOptions::new();
fetch_options.remote_callbacks(callbacks);
let _repo = match Repository::clone(remote, path) {
Ok(repo) => repo,
Err(e) => panic!("Failed to clone: {}", e),
};
PathBuf::from(path)
}
pub fn git_clone_here(remote: &str) -> PathBuf {
let repo_name = if remote.starts_with("git@") {
remote
.split(':')
.next_back()
.and_then(|s| s.strip_suffix(".git"))
.unwrap_or_else(|| {
panic!("Invalid SSH remote URL format: {}", remote);
})
} else {
remote
.split('/')
.next_back()
.and_then(|s| s.strip_suffix(".git"))
.unwrap_or_else(|| {
panic!("Invalid HTTPS remote URL format: {}", remote);
})
};
std::fs::create_dir(repo_name).unwrap_or_else(|e| {
panic!("Failed to create directory {}: {}", repo_name, e);
});
let mut callbacks = RemoteCallbacks::new();
let git_config = git2::Config::open_default().unwrap();
let mut handler = CredentialHandler::new(git_config);
callbacks.credentials(move |url, username, allowed| {
handler.try_next_credential(url, username, allowed)
});
let mut fetch_options = FetchOptions::new();
fetch_options.remote_callbacks(callbacks);
let _repo = match Repository::clone(remote, repo_name) {
Ok(repo) => repo,
Err(e) => panic!("Failed to clone: {}", e),
};
PathBuf::from(repo_name)
}
pub fn git_pull_ff(path: &str) -> PathBuf {
let mut callbacks = RemoteCallbacks::new();
let git_config = git2::Config::open_default().unwrap();
let mut handler = CredentialHandler::new(git_config);
callbacks.credentials(move |url, username, allowed| {
handler.try_next_credential(url, username, allowed)
});
let mut fetch_options = FetchOptions::new();
fetch_options.remote_callbacks(callbacks);
let repo = match Repository::open(path) {
Ok(repo) => repo,
Err(e) => panic!("Failed to open: {}", e),
};
let mut remote = match repo.find_remote("origin") {
Ok(r) => r,
Err(e) => panic!("Failed to find remote: {}", e),
};
match remote.fetch(&["main"], Some(&mut fetch_options), None) {
Ok(_) => (),
Err(e) => panic!("Failed to fetch: {}", e),
}
let fetch_head = match repo.find_reference("FETCH_HEAD") {
Ok(head) => head,
Err(e) => panic!("Failed to find FETCH_HEAD: {}", e),
};
let fetch_commit = match repo.reference_to_annotated_commit(&fetch_head) {
Ok(commit) => commit,
Err(e) => panic!("Failed to find commit: {}", e),
};
let analysis = match repo.merge_analysis(&[&fetch_commit]) {
Ok(analysis) => analysis,
Err(e) => panic!("Failed to analyze merge: {}", e),
};
if analysis.0.is_up_to_date() {
info!("Already up-to-date");
} else if analysis.0.is_fast_forward() {
info!("Performing fast-forward");
let refname = format!("refs/heads/{}", "main");
match repo.find_reference(&refname) {
Ok(mut r) => {
r.set_target(fetch_commit.id(), "Fast-Forward").unwrap();
}
Err(_) => {
repo.reference(&refname, fetch_commit.id(), true, "Fast-Forward")
.unwrap();
}
}
repo.set_head(&refname).unwrap();
repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))
.unwrap();
} else {
panic!("Can't fast-forward");
}
PathBuf::from(path)
}