139 lines
4.5 KiB
Rust
139 lines
4.5 KiB
Rust
//! 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<IndexPackRequest>) -> GitResult<IndexPackResponse> {
|
|
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(),
|
|
})
|
|
}
|
|
}
|