Skip to main content

thin_fetch/repo/
discover.rs

1use 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    /// # Errors
15    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}