This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-pack / src / archive.rs
4.6 kB 139 lines
1use std::io::{self, Read, Seek, SeekFrom}; 2 3use knot_git::{ArchiveFormat, ArchivePrefix, Repo}; 4use knot_types::Oid; 5 6use crate::error::PackError; 7use crate::pkt; 8 9struct Request { 10 treeish: String, 11 format: ArchiveFormat, 12 prefix: Option<ArchivePrefix>, 13} 14 15pub fn stream( 16 repo: &Repo, 17 request: &[u8], 18 sink: &mut dyn FnMut(&[u8]) -> io::Result<()>, 19) -> Result<(), PackError> { 20 let args = parse_arguments(request)?; 21 match build(repo, &args) { 22 Ok(mut spool) => { 23 let mut head = Vec::new(); 24 pkt::write_data(&mut head, b"ACK\n")?; 25 pkt::write_flush(&mut head)?; 26 emit(sink, &head)?; 27 std::iter::from_fn(|| { 28 let mut chunk = vec![0u8; pkt::MAX_BAND]; 29 match spool.read(&mut chunk) { 30 Ok(0) => None, 31 Ok(read) => { 32 chunk.truncate(read); 33 Some(Ok(chunk)) 34 } 35 Err(error) => Some(Err(error)), 36 } 37 }) 38 .try_for_each(|chunk| -> Result<(), PackError> { 39 let chunk = chunk.map_err(|error| PackError::Pack(error.to_string()))?; 40 let mut framed = Vec::new(); 41 pkt::write_band(&mut framed, &chunk)?; 42 emit(sink, &framed) 43 })?; 44 let mut tail = Vec::new(); 45 pkt::write_flush(&mut tail)?; 46 emit(sink, &tail) 47 } 48 Err(error) => { 49 let mut buf = Vec::new(); 50 pkt::write_data( 51 &mut buf, 52 format!("NACK {}\n", error.to_string().replace('\n', " ")).as_bytes(), 53 )?; 54 pkt::write_flush(&mut buf)?; 55 emit(sink, &buf) 56 } 57 } 58} 59 60fn emit(sink: &mut dyn FnMut(&[u8]) -> io::Result<()>, bytes: &[u8]) -> Result<(), PackError> { 61 sink(bytes).map_err(|error| PackError::Pack(error.to_string())) 62} 63 64fn parse_arguments(request: &[u8]) -> Result<Vec<String>, PackError> { 65 Ok(pkt::data_payloads(request)? 66 .iter() 67 .filter_map(|line| { 68 std::str::from_utf8(line) 69 .ok()? 70 .trim_end_matches('\n') 71 .strip_prefix("argument ") 72 .map(str::to_string) 73 }) 74 .collect()) 75} 76 77fn interpret(args: &[String]) -> Result<Request, PackError> { 78 let format = args 79 .iter() 80 .find_map(|arg| arg.strip_prefix("--format=")) 81 .map(format_from) 82 .unwrap_or(ArchiveFormat::Tar); 83 let prefix = args 84 .iter() 85 .find_map(|arg| arg.strip_prefix("--prefix=")) 86 .map(|raw| { 87 ArchivePrefix::new(raw).map_err(|_| { 88 PackError::Protocol("archive prefix must not escape archive root".to_string()) 89 }) 90 }) 91 .transpose()?; 92 let treeish = args 93 .iter() 94 .find(|arg| !arg.starts_with('-')) 95 .cloned() 96 .ok_or_else(|| PackError::Protocol("archive request has no tree-ish".to_string()))?; 97 Ok(Request { 98 treeish, 99 format, 100 prefix, 101 }) 102} 103 104fn format_from(value: &str) -> ArchiveFormat { 105 match value { 106 "zip" => ArchiveFormat::Zip, 107 "tar.gz" | "tgz" => ArchiveFormat::TarGz, 108 _ => ArchiveFormat::Tar, 109 } 110} 111 112fn build(repo: &Repo, args: &[String]) -> Result<std::fs::File, PackError> { 113 let request = interpret(args)?; 114 let id = repo 115 .resolve_revision(&request.treeish) 116 .ok_or_else(|| PackError::Protocol(format!("cannot resolve {}", request.treeish)))?; 117 let commit = ensure_public_commit(repo, id)?; 118 let tree = repo 119 .peel_to_tree(commit) 120 .map_err(|error| PackError::Pack(error.to_string()))?; 121 let mut spool = tempfile::tempfile().map_err(|error| PackError::Pack(error.to_string()))?; 122 repo.write_archive(tree, request.format, request.prefix.as_ref(), &mut spool) 123 .map_err(|error| PackError::Pack(error.to_string()))?; 124 spool 125 .seek(SeekFrom::Start(0)) 126 .map_err(|error| PackError::Pack(error.to_string()))?; 127 Ok(spool) 128} 129 130fn ensure_public_commit(repo: &Repo, id: Oid) -> Result<Oid, PackError> { 131 let unreachable = 132 || PackError::Protocol("tree-ish is not reachable from public ref".to_string()); 133 let commit = repo.peel_to_commit(id).map_err(|_| unreachable())?; 134 match repo.reachable_from_public(commit) { 135 Ok(true) => Ok(commit), 136 Ok(false) => Err(unreachable()), 137 Err(error) => Err(PackError::Pack(error.to_string())), 138 } 139}