use std::path::Path;
use sley_core::{GitError, ObjectFormat, Result};
use sley_formats::{Bundle, BundleReference};
use sley_odb::{FileObjectDatabase, install_bundle_pack, verify_bundle_prerequisites};
use sley_protocol::{
FetchHeadRecord, FetchRefUpdate, RefAdvertisement, parse_refspec, plan_fetch_ref_updates,
};
use sley_refs::{BundleRefUpdate, FileRefStore};
use crate::fetch::{
FetchOptions, fetch_refspecs_for_source, mark_tag_refspec_updates_not_for_merge,
order_bundle_fetch_all_tags_updates, retain_missing_auto_follow_tags, write_fetch_head,
write_fetch_head_records,
};
pub struct FetchBundleRequest<'a> {
pub git_dir: &'a Path,
pub format: ObjectFormat,
pub bundle_path: &'a str,
pub bundle: &'a Bundle,
pub refspecs: &'a [String],
pub options: &'a FetchOptions,
}
pub fn fetch_bundle(request: FetchBundleRequest<'_>) -> Result<()> {
let prerequisite_reader = FileObjectDatabase::from_git_dir(request.git_dir, request.format);
let references = if request.options.dry_run {
verify_bundle_prerequisites(request.bundle, &prerequisite_reader)?;
request.bundle.references.clone()
} else {
let database = FileObjectDatabase::from_git_dir(request.git_dir, request.format);
install_bundle_pack(request.bundle, &prerequisite_reader, &database)?.references
};
if request.refspecs.is_empty() {
if request.options.dry_run {
return Ok(());
}
if request.options.write_fetch_head {
let reference = bundle_default_fetch_reference(&references)?;
write_bundle_default_fetch_head(
request.git_dir,
request.bundle_path,
reference,
request.options.append,
)?;
}
return Ok(());
}
let refspecs =
fetch_refspecs_for_source(Vec::new(), request.refspecs, request.options.fetch_all_tags);
let mut fetched = bundle_fetch_refs(&references, &refspecs, request.options.auto_follow_tags)?;
if request.options.fetch_all_tags {
mark_tag_refspec_updates_not_for_merge(&mut fetched);
order_bundle_fetch_all_tags_updates(&mut fetched);
}
let store = FileRefStore::new(request.git_dir, request.format);
if !request.options.fetch_all_tags {
retain_missing_auto_follow_tags(&store, &mut fetched)?;
}
if request.options.dry_run {
return Ok(());
}
if request.options.write_fetch_head {
write_fetch_head(
request.git_dir,
request.bundle_path,
&fetched,
request.options.append,
)?;
}
let updates = fetched
.iter()
.filter_map(|fetched| {
fetched.dst.as_ref().map(|dst| BundleRefUpdate {
name: dst.clone(),
oid: fetched.oid,
})
})
.collect::<Vec<_>>();
store.apply_bundle_ref_updates(&updates, None)?;
Ok(())
}
fn bundle_default_fetch_reference(references: &[BundleReference]) -> Result<&BundleReference> {
references
.iter()
.find(|reference| reference.name == "HEAD")
.ok_or_else(|| GitError::reference_not_found("remote ref HEAD"))
}
fn write_bundle_default_fetch_head(
git_dir: &Path,
bundle_path: &str,
reference: &BundleReference,
append: bool,
) -> Result<()> {
let records = [FetchHeadRecord {
oid: reference.oid,
not_for_merge: false,
description: bundle_path.to_string(),
}];
write_fetch_head_records(git_dir, &records, append)?;
Ok(())
}
fn bundle_fetch_refs(
references: &[BundleReference],
refspecs: &[String],
auto_follow_tags: bool,
) -> Result<Vec<FetchRefUpdate>> {
let refs = references
.iter()
.map(|reference| RefAdvertisement {
oid: reference.oid,
name: reference.name.clone(),
capabilities: Vec::new(),
})
.collect::<Vec<_>>();
let refspecs = refspecs
.iter()
.map(|refspec| parse_refspec(refspec))
.collect::<Result<Vec<_>>>()?;
plan_fetch_ref_updates(&refs, &refspecs, auto_follow_tags)
}