thin_fetch/repo/
discover.rs1use crate::{Error, Parcel, WithError as _, bail};
2
3use super::{Repo, parse_response};
4
5#[derive(Debug, Default, Clone)]
6pub struct Capabilities {
7 pub agent: Option<String>,
8 pub ls_refs: Vec<String>,
9 pub fetch: Vec<String>,
10 pub object_format: Option<String>,
11}
12
13impl Repo {
14 pub fn discover(&self) -> Result<Capabilities, Error> {
16 let agent = ureq::Agent::config_builder()
17 .http_status_as_error(false)
18 .build()
19 .new_agent();
20
21 let res = agent
22 .get(format!("{}/info/refs?service=git-upload-pack", self.url))
23 .header("Git-Protocol", "version=2")
24 .call()
25 .with_err_msg("could not send HTTP request")?;
26
27 let mut res = parse_response(res)?;
28 res.ignore_end = true;
29 let parcel: Parcel = res.collect::<Result<Parcel, Error>>()?;
30
31 let mut sections = parcel.sections.into_iter();
32
33 let header = sections.next().with_err_msg("header not found")?;
34 let expected_header = ["# service=git-upload-pack\n"];
35 if &header != expected_header.as_slice() {
36 bail!("unexpected header: {header:?}");
37 }
38
39 let caps = sections.next().with_err_msg("capabilities not found")?;
40
41 let raw_caps = caps.chunks.into_iter();
42 let mut caps = Capabilities::default();
43
44 for cap in raw_caps {
45 let cap = str::from_utf8(&cap).with_err_msg("could not decode capability as utf-8")?;
46 let line = cap.trim_end_matches('\n');
47
48 if let Some(value) = line.strip_prefix("agent=") {
49 caps.agent.replace(value.to_owned());
50 }
51
52 if let Some(value) = line.strip_prefix("ls-refs=") {
53 caps.ls_refs.extend(value.split(' ').map(str::to_owned));
54 }
55
56 if let Some(value) = line.strip_prefix("fetch=") {
57 caps.fetch.extend(value.split(' ').map(str::to_owned));
58 }
59
60 if let Some(value) = line.strip_prefix("object-format=") {
61 caps.object_format.replace(value.to_owned());
62 }
63 }
64
65 Ok(caps)
66 }
67}