From 457e296d90e2f7c2808e216f2ef0608b70f76553 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 7 Jan 2026 21:35:56 +0000 Subject: Add Git protocol v2 support to fix modern git client compatibility Modern git clients (2.51.0+) default to protocol v2 and send the Git-Protocol header. The server must pass this to git processes via the GIT_PROTOCOL environment variable for proper negotiation. Changes: - Extract Git-Protocol header in HTTP layer (src/http/mod.rs) - Pass git_protocol parameter through all handler functions - Set GIT_PROTOCOL env var when spawning git subprocesses - Update all tests to pass None for backward compatibility This fixes hangs/timeouts when modern git clients connect to the server. Fixes issue discovered in work/2025-01-07-pr-clone-tag-sync-investigation.md --- src/git/handlers.rs | 10 +++++++--- src/git/subprocess.rs | 12 ++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) (limited to 'src/git') diff --git a/src/git/handlers.rs b/src/git/handlers.rs index ff55e34..017eee4 100644 --- a/src/git/handlers.rs +++ b/src/git/handlers.rs @@ -26,6 +26,7 @@ use crate::purgatory::Purgatory; pub async fn handle_info_refs( repo_path: PathBuf, service: GitService, + git_protocol: Option<&str>, ) -> Result>, GitError> { debug!( "Handling info/refs for {:?} with service {:?}", @@ -39,7 +40,7 @@ pub async fn handle_info_refs( } // Spawn git with --advertise-refs - let mut git = GitSubprocess::spawn(service, &repo_path, true).map_err(|e| { + let mut git = GitSubprocess::spawn(service, &repo_path, true, git_protocol).map_err(|e| { error!("Failed to spawn git process: {}", e); GitError::ProcessSpawnFailed(e) })?; @@ -102,6 +103,7 @@ pub async fn handle_info_refs( pub async fn handle_upload_pack( repo_path: PathBuf, request_body: Bytes, + git_protocol: Option<&str>, ) -> Result>, GitError> { debug!("Handling upload-pack for {:?}", repo_path); @@ -110,7 +112,7 @@ pub async fn handle_upload_pack( } // Spawn git upload-pack - let mut git = GitSubprocess::spawn(GitService::UploadPack, &repo_path, false) + let mut git = GitSubprocess::spawn(GitService::UploadPack, &repo_path, false, git_protocol) .map_err(GitError::ProcessSpawnFailed)?; // Write request to git's stdin @@ -181,6 +183,7 @@ pub async fn handle_upload_pack( /// * `identifier` - The repository identifier (d tag) for authorization lookup /// * `owner_pubkey` - The owner's public key (hex) from the URL path, scoping authorization /// * `git_data_path` - Base path for git repositories (for syncing to other owner repos) +/// * `git_protocol` - Optional Git protocol version (e.g., "version=2") #[allow(clippy::too_many_arguments)] pub async fn handle_receive_pack( repo_path: PathBuf, @@ -191,6 +194,7 @@ pub async fn handle_receive_pack( owner_pubkey: &str, purgatory: Arc, git_data_path: &str, + git_protocol: Option<&str>, ) -> Result>, GitError> { debug!("Handling receive-pack for {:?}", repo_path); @@ -236,7 +240,7 @@ pub async fn handle_receive_pack( }; // Spawn git receive-pack - let mut git = GitSubprocess::spawn(GitService::ReceivePack, &repo_path, false) + let mut git = GitSubprocess::spawn(GitService::ReceivePack, &repo_path, false, git_protocol) .map_err(GitError::ProcessSpawnFailed)?; // Write request to git's stdin diff --git a/src/git/subprocess.rs b/src/git/subprocess.rs index 2d9a981..acee726 100644 --- a/src/git/subprocess.rs +++ b/src/git/subprocess.rs @@ -22,10 +22,12 @@ impl GitSubprocess { /// * `service` - The Git service (upload-pack or receive-pack) /// * `repo_path` - Path to the bare Git repository /// * `advertise` - If true, run with --advertise-refs flag + /// * `git_protocol` - Optional Git protocol version (e.g., "version=2") pub fn spawn( service: GitService, repo_path: impl AsRef, advertise: bool, + git_protocol: Option<&str>, ) -> std::io::Result { let repo_path = repo_path.as_ref(); @@ -52,6 +54,12 @@ impl GitSubprocess { cmd.stdout(Stdio::piped()); cmd.stderr(Stdio::piped()); + // Set GIT_PROTOCOL environment variable if provided + // This enables Git protocol v2 support for modern git clients + if let Some(protocol) = git_protocol { + cmd.env("GIT_PROTOCOL", protocol); + } + let child = cmd.spawn()?; Ok(Self { child }) @@ -118,7 +126,7 @@ mod tests { #[tokio::test] async fn test_spawn_upload_pack_advertise() { let repo = create_bare_repo(); - let mut proc = GitSubprocess::spawn(GitService::UploadPack, repo.path(), true) + let mut proc = GitSubprocess::spawn(GitService::UploadPack, repo.path(), true, None) .expect("Failed to spawn git"); // Should have spawned successfully @@ -132,7 +140,7 @@ mod tests { #[tokio::test] async fn test_spawn_receive_pack() { let repo = create_bare_repo(); - let mut proc = GitSubprocess::spawn(GitService::ReceivePack, repo.path(), false) + let mut proc = GitSubprocess::spawn(GitService::ReceivePack, repo.path(), false, None) .expect("Failed to spawn git"); assert!(proc.stdout().is_some()); -- cgit v1.2.3