From 9a8c551adfada379704d594a6ff3862f13857b8e Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 21 Nov 2025 13:37:57 +0000 Subject: add git http handling --- src/git/mod.rs | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 src/git/mod.rs (limited to 'src/git/mod.rs') diff --git a/src/git/mod.rs b/src/git/mod.rs new file mode 100644 index 0000000..bd3b9e8 --- /dev/null +++ b/src/git/mod.rs @@ -0,0 +1,127 @@ +//! Git Smart HTTP Backend +//! +//! This module implements Git Smart HTTP protocol support for ngit-grasp. +//! It provides handlers for clone, fetch, and push operations over HTTP. +//! +//! # Architecture +//! +//! - `protocol` - Git pkt-line format parsing and utilities +//! - `subprocess` - Git process spawning and management +//! - `handlers` - HTTP request handlers for Git operations +//! +//! # URL Patterns +//! +//! The following URL patterns are supported: +//! - `GET //.git/info/refs?service=git-upload-pack` - Clone/fetch advertisement +//! - `GET //.git/info/refs?service=git-receive-pack` - Push advertisement +//! - `POST //.git/git-upload-pack` - Clone/fetch operation +//! - `POST //.git/git-receive-pack` - Push operation + +pub mod handlers; +pub mod protocol; +pub mod subprocess; + +use std::path::PathBuf; + +/// Parse a Git repository path from URL components +/// +/// Converts //.git/* to a filesystem path +/// +/// # Arguments +/// * `git_data_path` - Base directory for Git repositories +/// * `npub` - The npub (Nostr public key in bech32 format) +/// * `identifier` - The repository identifier +/// +/// # Returns +/// Path to the bare Git repository +pub fn resolve_repo_path(git_data_path: &str, npub: &str, identifier: &str) -> PathBuf { + // Remove .git suffix if present + let identifier = identifier.strip_suffix(".git").unwrap_or(identifier); + + PathBuf::from(git_data_path) + .join(npub) + .join(format!("{}.git", identifier)) +} + +/// Extract npub and identifier from a Git URL path +/// +/// Parses paths like `//.git/info/refs` +/// +/// Returns (npub, identifier, subpath) where subpath is the part after .git/ +pub fn parse_git_url(path: &str) -> Option<(&str, &str, &str)> { + // Remove leading slash + let path = path.strip_prefix('/').unwrap_or(path); + + // Split into components + let parts: Vec<&str> = path.splitn(3, '/').collect(); + + if parts.len() < 3 { + return None; + } + + let npub = parts[0]; + let repo_part = parts[1]; + let subpath = parts[2]; + + // Extract identifier (remove .git suffix if present for the middle part) + let identifier = if repo_part.ends_with(".git") { + &repo_part[..repo_part.len() - 4] + } else { + repo_part + }; + + Some((npub, identifier, subpath)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_resolve_repo_path() { + let path = resolve_repo_path( + "/data/git", + "npub1abc123", + "my-repo" + ); + assert_eq!( + path, + PathBuf::from("/data/git/npub1abc123/my-repo.git") + ); + } + + #[test] + fn test_resolve_repo_path_with_git_suffix() { + let path = resolve_repo_path( + "/data/git", + "npub1abc123", + "my-repo.git" + ); + assert_eq!( + path, + PathBuf::from("/data/git/npub1abc123/my-repo.git") + ); + } + + #[test] + fn test_parse_git_url_info_refs() { + let (npub, id, subpath) = parse_git_url("/npub1abc/repo.git/info/refs").unwrap(); + assert_eq!(npub, "npub1abc"); + assert_eq!(id, "repo"); + assert_eq!(subpath, "info/refs"); + } + + #[test] + fn test_parse_git_url_upload_pack() { + let (npub, id, subpath) = parse_git_url("/npub1abc/repo.git/git-upload-pack").unwrap(); + assert_eq!(npub, "npub1abc"); + assert_eq!(id, "repo"); + assert_eq!(subpath, "git-upload-pack"); + } + + #[test] + fn test_parse_git_url_invalid() { + assert!(parse_git_url("/npub1abc").is_none()); + assert!(parse_git_url("/npub1abc/repo").is_none()); + } +} \ No newline at end of file -- cgit v1.2.3