use std::io::Write;
use std::sync::atomic::AtomicBool;
use girt::fetch::{AdvertisedRef, Advertisement};
use girt::push::{ForcePolicy, PreparedPush, PushCommand, PushLimits};
use girt::refs::RefName;
use girt::remote::{Mapping, RefSource, Remote};
use girt::{InitKind, ObjectId, Repository};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let directory = tempfile::tempdir()?;
let repo = Repository::init(
girt::ObjectFormat::Sha1,
directory.path().join("repo"),
InitKind::Bare,
)?;
let mut file = std::fs::OpenOptions::new()
.append(true)
.open(repo.common_dir().join("config"))?;
file.write_all(b"\n[remote \"origin\"]\nurl=https://example.invalid/repo\nfetch=refs/tags/*:refs/tags/*\npush=refs/tags/local:refs/tags/published\n")?;
let repo = Repository::open(repo.git_dir())?;
let remote = Remote::find(repo.config(), b"origin")?.ok_or("missing origin")?;
assert_eq!(
remote.fetch_url(),
Some(b"https://example.invalid/repo".as_slice())
);
let tag_name = RefName::new("refs/tags/remote")?;
let advertised_id = ObjectId::for_blob(girt::ObjectFormat::Sha1, b"advertised fixture");
let advertisement = Advertisement::new(
vec![AdvertisedRef::new(
tag_name.clone(),
advertised_id,
girt::fetch::AdvertisementKind::Tip,
)],
Vec::new(),
);
let mut planned = None;
let mut select = |advertisement: &Advertisement| {
let result = remote.fetch_refspecs().map_advertisement(advertisement);
let ids = result
.as_ref()
.map_or_else(|_| Vec::new(), |plan| selected_ids(plan));
planned = Some(result);
ids
};
let wants = select(&advertisement);
let fetch_plan = planned.ok_or("selection callback was not invoked")??;
assert_eq!(wants, vec![advertised_id]);
assert_eq!(fetch_plan[0].destination, Some(tag_name));
let local_id = repo.loose_objects().write_blob(b"local tag target")?;
let local_sources = [RefSource {
name: RefName::new("refs/tags/local")?,
id: local_id,
}];
let push_plan = remote.push_refspecs().map(&local_sources)?;
let commands = creation_commands(&push_plan)?;
assert_eq!(commands[0].name.as_bytes(), b"refs/tags/published");
let objects = repo.objects()?;
let cancel = AtomicBool::new(false);
let _prepared = PreparedPush::new(&objects, commands, &[], PushLimits::default(), &cancel)?;
println!(
"Planned {} fetch selection and {} conditional push creation; sent nothing",
wants.len(),
push_plan.len()
);
Ok(())
}
fn selected_ids(plan: &[Mapping]) -> Vec<ObjectId> {
let mut ids: Vec<_> = plan
.iter()
.filter_map(|mapping| mapping.source.as_ref())
.map(|source| source.id)
.collect();
ids.sort();
ids.dedup();
ids
}
fn creation_commands(plan: &[Mapping]) -> Result<Vec<PushCommand>, Box<dyn std::error::Error>> {
let mut commands = Vec::new();
for mapping in plan {
if mapping.force == girt::remote::ForceIntent::Requested {
return Err("this consumer has not authorized force".into());
}
let source = mapping
.source
.as_ref()
.ok_or("push transport does not support deletion")?;
let destination = mapping
.destination
.as_ref()
.ok_or("push requires a destination")?;
commands.push(
PushCommand::new(destination.clone(), None, source.id)
.with_force(ForcePolicy::FastForwardOnly),
);
}
Ok(commands)
}