use crate::bare::GitBare; use crate::error::GitResult; use crate::pb::*; const MAX_RAW_DIFF_OUTPUT_BYTES: usize = 64 * 1024 * 1024; impl GitBare { /// Stream raw diff output. pub fn raw_diff(&self, request: RawDiffRequest) -> GitResult> { let base = &request.base; let head = &request.head; crate::sanitize::validate_revision(base)?; crate::sanitize::validate_revision(head)?; let mut args = vec![ "--git-dir".to_string(), self.bare_dir.to_string_lossy().into_owned(), "diff".to_string(), ]; let mut pathspecs = Vec::new(); // Apply options if present if let Some(ref opts) = request.options { if opts.recursive { args.push("--recursive".to_string()); } if opts.include_binary { args.push("--binary".to_string()); } else { args.push("--no-binary".to_string()); } for pathspec in &opts.pathspec { crate::sanitize::validate_file_path(pathspec)?; pathspecs.push(pathspec.clone()); } } args.push(base.clone()); args.push(head.clone()); if !pathspecs.is_empty() { args.push("--".to_string()); args.extend(pathspecs); } let output = std::process::Command::new("git") .args(&args) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; if !output.status.success() { return Err(crate::error::GitError::CommandFailed { status_code: output.status.code(), stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(), }); } if output.stdout.len() > MAX_RAW_DIFF_OUTPUT_BYTES { return Err(crate::error::GitError::InvalidArgument(format!( "raw diff output too large (max {MAX_RAW_DIFF_OUTPUT_BYTES} bytes)" ))); } // Chunk the output for streaming const CHUNK_SIZE: usize = 32768; let data = output.stdout; let chunks: Vec = data .chunks(CHUNK_SIZE) .map(|c| RawDiffResponse { data: c.to_vec() }) .collect(); Ok(chunks) } /// Stream raw patch (format-patch) output. pub fn raw_patch(&self, request: RawPatchRequest) -> GitResult> { crate::sanitize::validate_revision(&request.base)?; crate::sanitize::validate_revision(&request.head)?; let range = format!("{}..{}", request.base, request.head); let output = std::process::Command::new("git") .args([ "--git-dir", &self.bare_dir.to_string_lossy(), "format-patch", "--stdout", &range, ]) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; if !output.status.success() { return Err(crate::error::GitError::CommandFailed { status_code: output.status.code(), stderr: String::from_utf8_lossy(&output.stderr).trim().to_string(), }); } if output.stdout.len() > MAX_RAW_DIFF_OUTPUT_BYTES { return Err(crate::error::GitError::InvalidArgument(format!( "raw patch output too large (max {MAX_RAW_DIFF_OUTPUT_BYTES} bytes)" ))); } const CHUNK_SIZE: usize = 32768; let data = output.stdout; let chunks: Vec = data .chunks(CHUNK_SIZE) .map(|c| RawPatchResponse { data: c.to_vec() }) .collect(); Ok(chunks) } }