git-remote-iroh 0.1.0

Git remote protocol support for https://www.iroh.computer
Documentation
use std::{fmt::Display, str::FromStr};

mod client;
mod keys;
mod server;

pub use client::{Client, ClientArgs};
pub use server::{Server, ServerArgs};

/// ALPN for git-remote-iroh connections.
pub const GIT_IROH_ALPN: &[u8] = b"git/iroh/0";

/// Stream header sent at the start of a git stream.
/// Identifies whether this is a push (receive-pack) or fetch (upload-pack).
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GitOp {
    /// Client wants to push (server runs git-receive-pack)
    Push,
    /// Client wants to fetch (server runs git-upload-pack)
    Fetch,
}

impl GitOp {
    pub fn to_byte(self) -> u8 {
        match self {
            GitOp::Push => 1,
            GitOp::Fetch => 2,
        }
    }

    pub fn from_byte(b: u8) -> Option<Self> {
        match b {
            1 => Some(GitOp::Push),
            2 => Some(GitOp::Fetch),
            _ => None,
        }
    }
}

impl FromStr for GitOp {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s {
            "git-upload-pack" => GitOp::Push,
            "git-receive-pack" => GitOp::Fetch,
            _ => anyhow::bail!("unsupported command {s}"),
        })
    }
}

impl Display for GitOp {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            GitOp::Fetch => f.write_str("git-receive-pack"),
            GitOp::Push => f.write_str("git-upload-pack"),
        }
    }
}

/// Parses `iroh://<node-id>` URL.
fn parse_iroh_url(url: &str) -> Result<iroh::PublicKey, anyhow::Error> {
    let node_id_str = url.strip_prefix("iroh://").ok_or(anyhow::anyhow!(
        "invalid iroh URL (expected iroh://): {}",
        url
    ))?;

    Ok(node_id_str.parse::<iroh::PublicKey>()?)
}