use std::io::Write; use crate::bare::GitBare; use crate::error::{GitError, GitResult}; use crate::pb::{IndexPackRequest, IndexPackResponse}; impl GitBare { /// Index a pack file from streamed input. /// /// Client-streaming → unary response. /// Collects all input chunks into a single pack, then runs `git index-pack`. pub fn index_pack(&self, inputs: Vec) -> GitResult { // Reassemble all chunks into a single pack data buffer let mut pack_data = Vec::new(); let mut strict = false; let mut keep = false; for input in &inputs { pack_data.extend_from_slice(&input.data); if input.strict { strict = true; } if input.keep { keep = true; } } if pack_data.is_empty() { return Err(GitError::InvalidArgument("empty pack data".into())); } let pack_dir = self.bare_dir.join("objects").join("pack"); std::fs::create_dir_all(&pack_dir).map_err(GitError::Io)?; // Write pack data to a unique temp file in the pack directory. let mut tmp_file = tempfile::Builder::new() .prefix("tmp_index_pack_") .tempfile_in(&pack_dir) .map_err(GitError::Io)?; tmp_file.write_all(&pack_data).map_err(GitError::Io)?; let tmp_path = tmp_file.path().to_path_buf(); let mut args = vec![ "--git-dir".to_string(), self.bare_dir.to_string_lossy().into_owned(), "index-pack".to_string(), ]; if strict { args.push("--strict".into()); } if keep { args.push("--keep".into()); } args.push(tmp_path.to_string_lossy().into_owned()); let result = duct::cmd("git", &args) .stdout_capture() .stderr_capture() .unchecked() .run()?; drop(tmp_file); if !result.status.success() { return Err(GitError::CommandFailed { status_code: result.status.code(), stderr: String::from_utf8_lossy(&result.stderr).into_owned(), }); } // Parse the output to extract the pack hash let output = String::from_utf8_lossy(&result.stdout); let stderr = String::from_utf8_lossy(&result.stderr); let all_output = format!("{output}\n{stderr}"); // git index-pack outputs the .idx and .pack filenames // e.g. "... pack-.pack ... pack-.idx" let pack_hash = all_output .lines() .filter_map(|line| { // Look for the hash after "pack-" and before ".idx" or ".pack" let trimmed = line.trim(); if let Some(idx) = trimmed.find("pack-") { let rest = &trimmed[idx + 5..]; if let Some(end) = rest.find('.') { let hex = &rest[..end]; if hex.len() == 40 || hex.len() == 64 { return Some(hex.to_string()); } } } None }) .next(); // Try to get object count from .idx if it exists let mut object_count = 0u64; if let Some(ref hash) = pack_hash { let idx_path = pack_dir.join(format!("pack-{}.idx", hash)); if idx_path.exists() { let verify = duct::cmd( "git", [ "--git-dir", self.bare_dir.to_string_lossy().as_ref(), "verify-pack", "-v", idx_path.to_string_lossy().as_ref(), ], ) .stdout_capture() .stderr_capture() .unchecked() .run(); if let Ok(v) = verify { let out = String::from_utf8_lossy(&v.stdout); object_count = out .lines() .filter(|l| { let parts: Vec<&str> = l.split_whitespace().collect(); parts.len() >= 3 && parts .first() .map(|s| s.len() == 40 || s.len() == 64) .unwrap_or(false) }) .count() as u64; } } } Ok(IndexPackResponse { pack_hash: pack_hash.map(|h| self.oid_to_pb(h)), object_count, stderr: stderr.into_owned(), }) } }