feat(core): implement Git repository operations with gRPC services
- Add advertise_refs functionality for Git protocol communication - Implement archive service with TAR/ZIP format support and streaming - Create blame service for Git file annotation with line tracking - Add branch management including create, delete, rename and compare operations - Implement merge checking with conflict detection and fast-forward handling - Add cherry-pick functionality for applying commits between branches - Integrate gix library for Git repository operations and object handling - Add comprehensive test suite covering all Git operations - Implement proper error handling and repository validation - Add pagination support for large result sets - Create protobuf definitions for all Git operations and data structures - Add build system for gRPC code generation and dependency management
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
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<IndexPackRequest>) -> GitResult<IndexPackResponse> {
|
||||
// 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-<hex>.pack ... pack-<hex>.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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user