//! Copyright (c) 2022-2026 GitDataAi All rights reserved. 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. /// Writes each chunk directly to a temp file to avoid buffering /// the entire pack in memory. pub fn index_pack(&self, inputs: Vec) -> GitResult { let mut strict = false; let mut keep = false; let mut has_data = false; let pack_dir = self.bare_dir.join("objects").join("pack"); std::fs::create_dir_all(&pack_dir).map_err(GitError::Io)?; let mut tmp_file = tempfile::Builder::new() .prefix("tmp_index_pack_") .tempfile_in(&pack_dir) .map_err(GitError::Io)?; for input in &inputs { if !input.data.is_empty() { tmp_file.write_all(&input.data).map_err(GitError::Io)?; has_data = true; } if input.strict { strict = true; } if input.keep { keep = true; } } if !has_data { return Err(GitError::InvalidArgument("empty pack data".into())); } tmp_file.flush().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".into(), ]; 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(), }); } let output = String::from_utf8_lossy(&result.stdout); let stderr = String::from_utf8_lossy(&result.stderr); let all_output = format!("{output}\n{stderr}"); let pack_hash = all_output .lines() .filter_map(|line| { 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(); let mut object_count = 0u64; if let Some(ref hash) = pack_hash { let idx_path = pack_dir.join(format!("pack-{hash}.idx")); 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(), }) } }