girt 0.18.0

An incremental, idiomatic Rust implementation of Git
Documentation
//! Run `cargo run --example remote_plan`; only a disposable local fixture is written.
//! No discovery session, transfer, ref update or network operation is executed.
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")?;

    // Configuration is a snapshot: reopen after fixture setup.
    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())
    );

    // Supply the Advertisement received by an existing fetch adapter's selection callback.
    // This example uses a synthetic advertisement instead of connecting to a server.
    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
    };

    // Pass `select` to receive_local/receive_http/receive_ssh in a real transfer. Here we call it
    // directly, then check its saved error before accepting any transfer outcome.
    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));
    // Current fetch callbacks return IDs only, so mapping errors travel through the saved result.
    // Retain the plan for a separate conditional ref transaction after validated installation.

    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)?;

    // Sending would be a separate, authorized call to push::send_local/send_http/send_ssh.
    // Select and validate the endpoint from remote.push_urls() before choosing an adapter.
    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
}

// Example consumer policy: create destinations only, reject configured force intent and deletion.
// A consumer replacing refs must independently supply exact observed old values and authorize
// force.
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)
}