git_policy_cli/
standards_review_source.rs1use super::standards_review_input::{LandedCommit, MAX_EXCERPT_FILES};
16use super::transaction_git::{GitContext, GitRequest, run_git};
17use std::collections::BTreeMap;
18use std::ffi::OsString;
19use std::path::Path;
20
21pub const MAX_PATCH_BYTES: usize = 1 << 26;
23
24fn read_patch(context: &GitContext, cwd: &Path, sha: &str) -> Result<String, String> {
34 let request: GitRequest = GitRequest::new(cwd, &["show", "--format=", "--patch", "--no-color", "--no-ext-diff", sha]);
35 let output = run_git(context, &request).map_err(|error| return format!("starting git show failed: {error}"))?;
36 if !output.succeeded() {
37 return Err(format!("git show {sha} failed: {}", output.error_text()));
38 }
39 if output.stdout.len() > MAX_PATCH_BYTES {
40 return Err(format!("the patch of {sha} is over {MAX_PATCH_BYTES} bytes"));
41 }
42 return Ok(String::from_utf8_lossy(output.stdout.as_slice()).into_owned());
43}
44
45pub fn parse_batch(output: &[u8], names: &[String]) -> Result<BTreeMap<String, String>, String> {
55 let mut texts: BTreeMap<String, String> = BTreeMap::new();
56 let mut offset: usize = 0;
57 for name in names {
58 let Some(relative) = output[offset..].iter().position(|byte| return *byte == b'\n') else {
59 return Err(format!("git cat-file omitted the header for {name}"));
60 };
61 let header: String = String::from_utf8_lossy(&output[offset..offset + relative]).into_owned();
62 offset += relative + 1;
63 let fields: Vec<&str> = header.split(' ').collect();
64 if fields.len() == 2 && fields[1] == "missing" {
65 continue;
66 }
67 let size: usize = match (fields.len(), fields.get(1), fields.get(2).and_then(|text| return text.parse::<usize>().ok())) {
68 (3, Some(&"blob"), Some(size)) => size,
69 _ => return Err(format!("git cat-file returned an unexpected header for {name}: {header}")),
70 };
71 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}"))?;
72 texts.insert(name.clone(), String::from_utf8_lossy(&output[offset..end]).into_owned());
73 offset = end + 1;
74 }
75 return Ok(texts);
76}
77
78pub fn read_landed_commit(context: &GitContext, cwd: &Path, sha: &str, subject: &str, code_files: &[String]) -> Result<LandedCommit, String> {
89 let patch: String = read_patch(context, cwd, sha)?;
90 let wanted: Vec<String> = code_files.iter().take(MAX_EXCERPT_FILES).cloned().collect();
91 let specs: String = wanted.iter().map(|name| return format!("{sha}:{name}\n")).collect();
92 let mut request: GitRequest = GitRequest::new(cwd, &[OsString::from("cat-file"), OsString::from("--batch")]);
93 request.input = Some(specs.into_bytes());
94 let output = run_git(context, &request).map_err(|error| return format!("starting git cat-file failed: {error}"))?;
95 if !output.succeeded() {
96 return Err(format!("git cat-file failed: {}", output.error_text()));
97 }
98 let contents: BTreeMap<String, String> = parse_batch(output.stdout.as_slice(), wanted.as_slice())?;
99 return Ok(LandedCommit { sha: String::from(sha), subject: String::from(subject), patch, contents });
100}
101
102#[cfg(test)]
104#[path = "standards_review_source_tests.rs"]
105mod tests;