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