use std::collections::HashMap;
use std::path::Path;
use git2::{Cred, FetchOptions, RemoteCallbacks, Repository};
use crate::cli::args::PullArgs;
use crate::error::{AgitError, Result};
use crate::search::incremental;
pub fn execute(args: PullArgs) -> Result<()> {
let cwd = std::env::current_dir()?;
let agit_dir = cwd.join(".agit");
if !agit_dir.exists() {
return Err(AgitError::NotInitialized);
}
let repo = Repository::discover(&cwd)?;
let refs_before = capture_agit_refs(&repo)?;
let mut remote = repo.find_remote(&args.remote).map_err(|e| {
AgitError::InvalidArgument(format!("Remote '{}' not found: {}", args.remote, e))
})?;
let mut callbacks = RemoteCallbacks::new();
callbacks.credentials(|_url, username_from_url, allowed_types| {
if allowed_types.contains(git2::CredentialType::SSH_KEY) {
if let Some(username) = username_from_url {
return Cred::ssh_key_from_agent(username);
}
}
if allowed_types.contains(git2::CredentialType::USER_PASS_PLAINTEXT) {
return Cred::credential_helper(
&Repository::discover(".").unwrap().config().unwrap(),
_url,
username_from_url,
);
}
if allowed_types.contains(git2::CredentialType::DEFAULT) {
return Cred::default();
}
Err(git2::Error::from_str("No suitable credentials found"))
});
callbacks.transfer_progress(|progress| {
if progress.total_objects() > 0 {
print!(
"\rFetching: {}/{} objects ({} bytes)",
progress.received_objects(),
progress.total_objects(),
progress.received_bytes()
);
}
true
});
let mut fetch_options = FetchOptions::new();
fetch_options.remote_callbacks(callbacks);
let refspec = "refs/agit/*:refs/agit/*";
println!("Fetching agit refs from '{}'...", args.remote);
remote.fetch(&[refspec], Some(&mut fetch_options), None)?;
let refs: Vec<_> = repo
.references_glob("refs/agit/*")?
.filter_map(|r| r.ok())
.collect();
println!(
"\nFetched {} agit ref(s) from '{}'",
refs.len(),
args.remote
);
if !refs.is_empty() {
println!("\nAvailable refs:");
for reference in refs {
if let Some(name) = reference.name() {
if let Some(branch) = name.strip_prefix("refs/agit/heads/") {
let short_hash = reference
.target()
.map(|oid| oid.to_string()[..7].to_string())
.unwrap_or_else(|| "???????".to_string());
println!(" {} -> {}", branch, short_hash);
}
}
}
}
let refs_after = capture_agit_refs(&repo)?;
index_after_pull(&cwd, &agit_dir, &refs_before, &refs_after);
Ok(())
}
fn capture_agit_refs(repo: &Repository) -> Result<HashMap<String, String>> {
let mut refs = HashMap::new();
if let Ok(references) = repo.references_glob("refs/agit/heads/*") {
for reference in references.filter_map(|r| r.ok()) {
if let (Some(name), Some(target)) = (reference.name(), reference.target()) {
if let Some(branch) = name.strip_prefix("refs/agit/heads/") {
refs.insert(branch.to_string(), target.to_string());
}
}
}
}
Ok(refs)
}
fn index_after_pull(
repo_path: &Path,
agit_dir: &Path,
refs_before: &HashMap<String, String>,
refs_after: &HashMap<String, String>,
) {
if refs_before == refs_after {
return;
}
match incremental::index_new_commits(repo_path, agit_dir, refs_before, refs_after) {
Ok((count, was_full_rebuild)) => {
if count > 0 {
if was_full_rebuild {
println!("\nRebuilt search index ({} entries)", count);
} else {
println!("\nIndexed {} new search entries", count);
}
}
},
Err(e) => {
eprintln!("\nWarning: Failed to update search index: {}", e);
eprintln!("Run 'agit search rebuild' to rebuild manually.");
},
}
}