use super::standards_review_input::{LandedCommit, MAX_EXCERPT_FILES};
use super::transaction_git::{GitContext, GitRequest, run_git};
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::path::Path;
pub const MAX_PATCH_BYTES: usize = 1 << 26;
fn read_patch(context: &GitContext, cwd: &Path, sha: &str) -> Result<String, String> {
let request: GitRequest = GitRequest::new(cwd, &["show", "--format=", "--patch", "--no-color", "--no-ext-diff", sha]);
let output = run_git(context, &request).map_err(|error| return format!("starting git show failed: {error}"))?;
if !output.succeeded() {
return Err(format!("git show {sha} failed: {}", output.error_text()));
}
if output.stdout.len() > MAX_PATCH_BYTES {
return Err(format!("the patch of {sha} is over {MAX_PATCH_BYTES} bytes"));
}
return Ok(String::from_utf8_lossy(output.stdout.as_slice()).into_owned());
}
pub fn parse_batch(output: &[u8], names: &[String]) -> Result<BTreeMap<String, String>, String> {
let mut texts: BTreeMap<String, String> = BTreeMap::new();
let mut offset: usize = 0;
for name in names {
let Some(relative) = output[offset..].iter().position(|byte| return *byte == b'\n') else {
return Err(format!("git cat-file omitted the header for {name}"));
};
let header: String = String::from_utf8_lossy(&output[offset..offset + relative]).into_owned();
offset += relative + 1;
let fields: Vec<&str> = header.split(' ').collect();
if fields.len() == 2 && fields[1] == "missing" {
continue;
}
let size: usize = match (fields.len(), fields.get(1), fields.get(2).and_then(|text| return text.parse::<usize>().ok())) {
(3, Some(&"blob"), Some(size)) => size,
_ => return Err(format!("git cat-file returned an unexpected header for {name}: {header}")),
};
let end: usize = offset.checked_add(size).filter(|end| return *end < output.len() && output[*end] == b'\n').ok_or_else(|| return format!("git cat-file returned truncated content for {name}"))?;
texts.insert(name.clone(), String::from_utf8_lossy(&output[offset..end]).into_owned());
offset = end + 1;
}
return Ok(texts);
}
pub fn read_names(context: &GitContext, cwd: &Path, sha: &str) -> Result<Vec<String>, String> {
let request: GitRequest = GitRequest::new(cwd, &["diff-tree", "--no-commit-id", "--name-only", "-r", "-z", sha]);
let output = run_git(context, &request).map_err(|error| return format!("starting git diff-tree failed: {error}"))?;
if !output.succeeded() {
return Err(format!("git diff-tree {sha} failed: {}", output.error_text()));
}
return Ok(output.stdout.split(|byte| return *byte == 0).filter(|name| return !name.is_empty()).map(|name| return String::from_utf8_lossy(name).into_owned()).collect());
}
pub fn read_message(context: &GitContext, cwd: &Path, sha: &str) -> Result<String, String> {
let request: GitRequest = GitRequest::new(cwd, &["show", "--no-patch", "--format=%B", sha]);
let output = run_git(context, &request).map_err(|error| return format!("starting git show failed: {error}"))?;
if !output.succeeded() {
return Err(format!("git show {sha} failed: {}", output.error_text()));
}
return Ok(String::from_utf8_lossy(output.stdout.as_slice()).into_owned());
}
pub fn read_landed_commit(context: &GitContext, cwd: &Path, sha: &str, subject: &str, code_files: &[String]) -> Result<LandedCommit, String> {
let patch: String = read_patch(context, cwd, sha)?;
let wanted: Vec<String> = code_files.iter().take(MAX_EXCERPT_FILES).cloned().collect();
let specs: String = wanted.iter().map(|name| return format!("{sha}:{name}\n")).collect();
let mut request: GitRequest = GitRequest::new(cwd, &[OsString::from("cat-file"), OsString::from("--batch")]);
request.input = Some(specs.into_bytes());
let output = run_git(context, &request).map_err(|error| return format!("starting git cat-file failed: {error}"))?;
if !output.succeeded() {
return Err(format!("git cat-file failed: {}", output.error_text()));
}
let contents: BTreeMap<String, String> = parse_batch(output.stdout.as_slice(), wanted.as_slice())?;
return Ok(LandedCommit { sha: String::from(sha), subject: String::from(subject), patch, contents });
}
#[cfg(test)]
#[path = "standards_review_source_tests.rs"]
mod tests;