use super::*;
use std::io::{Seek, Write};
pub(super) fn read_originals(
root: &Path,
head: &str,
paths: &BTreeMap<String, bool>,
cache: &SnapshotCache,
options: &ReviewReadOptions<'_>,
) -> Result<BTreeMap<String, String>, String> {
let requested: Vec<_> = paths
.iter()
.take(MAX_FILES)
.filter(|(path, original)| {
**original
&& !path.contains(['\n', '\r'])
&& !cache
.files
.iter()
.any(|file| file.path == **path && file.notice.is_none())
})
.map(|(path, _)| path)
.collect();
if requested.is_empty() {
return Ok(BTreeMap::new());
}
let mut input = tempfile::tempfile().map_err(|error| error.to_string())?;
for path in &requested {
options.check()?;
validate_path(path)?;
writeln!(input, "{}:{path}", head.trim_end()).map_err(|error| error.to_string())?;
}
input.rewind().map_err(|error| error.to_string())?;
let output = run_with_input(
root,
&["cat-file", "--batch"],
MAX_TOTAL_SOURCE + MAX_FILES * 128,
options,
input.into(),
)?;
if output.stdout_truncated
|| !output.status.is_some_and(|status| status.success())
|| output.stdout.contains('\u{fffd}')
{
return Ok(BTreeMap::new());
}
Ok(parse_originals(&output.stdout, &requested).unwrap_or_default())
}
fn parse_originals(output: &str, paths: &[&String]) -> Option<BTreeMap<String, String>> {
let mut remaining = output;
let mut originals = BTreeMap::new();
for path in paths {
let (header, rest) = remaining.split_once('\n')?;
let mut fields = header.split_whitespace();
let _object = fields.next()?;
let kind = fields.next()?;
let size: usize = fields.next()?.parse().ok()?;
if fields.next().is_some() {
return None;
}
let content = rest.get(..size)?;
remaining = rest.get(size..)?.strip_prefix('\n')?;
if kind == "blob" && size <= MAX_SOURCE {
originals.insert((*path).clone(), content.to_owned());
}
}
remaining.is_empty().then_some(originals)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn batch_framing_preserves_embedded_headers_newlines_and_binary_markers() {
let paths = ["one.rs".to_owned(), "two.rs".to_owned()];
let first = "fake blob 123\n\0\n";
let second = "fn main() {}";
let output = format!(
"abc blob {}\n{first}\ndef blob {}\n{second}\n",
first.len(),
second.len()
);
let parsed = parse_originals(&output, &[&paths[0], &paths[1]]).unwrap();
assert_eq!(parsed[&paths[0]], first);
assert_eq!(parsed[&paths[1]], second);
assert!(parse_originals(&output[..output.len() - 1], &[&paths[0], &paths[1]]).is_none());
}
}