1use std::str::FromStr;
2use std::{collections::HashMap, io::Read};
3
4use ureq::Body;
5use ureq::http::Response;
6use ureq::{BodyReader, http::StatusCode};
7
8use crate::error::{Error, WithError as _, bail};
9use crate::object::Object;
10use crate::parcel::{Parcel, ParcelDecoder};
11use crate::{Execute as _, HashKind, LsRefs, Reference};
12
13pub struct Repo {
27 url: String,
28 hash_kind: HashKind,
29 object_cache: HashMap<String, Object>,
30}
31
32impl Repo {
33 pub fn new_discover<S: Into<String>>(url: S) -> Result<Self, Error> {
40 let mut repo = Self {
41 url: url.into(),
42 hash_kind: HashKind::Sha1,
43 object_cache: HashMap::new(),
44 };
45
46 let res = repo.discover().with_err_msg("could not discover")?;
47
48 if let Some(format) = &res.object_format {
49 repo.hash_kind = HashKind::from_str(format)?;
50 } else {
51 bail!("remote repo did not send object-format");
52 }
53
54 Ok(repo)
55 }
56
57 pub fn new<S: Into<String>>(url: S, hash_kind: HashKind) -> Self {
61 Self {
62 url: url.into(),
63 hash_kind,
64 object_cache: HashMap::new(),
65 }
66 }
67
68 pub fn call<C: Into<Parcel>>(&self, cmd: C) -> Result<Parcel, Error> {
75 let dec = self.stream(cmd)?;
76 dec.collect()
77 }
78
79 pub fn stream<C: Into<Parcel>>(
86 &self,
87 cmd: C,
88 ) -> Result<ParcelDecoder<BodyReader<'static>>, Error> {
89 let body: Parcel = cmd.into();
90
91 let mut data: Vec<u8> = vec![];
92 body.write_to(&mut data)
93 .with_err_msg("could not serialize parcel")?;
94
95 let agent = ureq::Agent::config_builder()
96 .http_status_as_error(false)
97 .build()
98 .new_agent();
99
100 let res = agent
101 .post(format!("{}/git-upload-pack", self.url))
102 .header("Git-Protocol", "version=2")
103 .header("Content-Type", "application/x-git-upload-pack-request")
104 .header("Accept", "application/x-git-upload-pack-result")
105 .send(data)
106 .with_err_msg("could not send HTTP request")?;
107
108 parse_response(res)
109 }
110
111 pub fn list_refs(&self) -> Result<Vec<Reference>, Error> {
117 let cmd = LsRefs {
118 peel: true,
119 symrefs: true,
120 unborn: true,
121 ref_prefix: None,
122 hash_kind: self.hash_kind,
123 };
124
125 cmd.execute(self)
126 }
127
128 pub fn get_ref(&self, needle: &str) -> Result<Option<Reference>, Error> {
134 let refs = self
135 .list_refs_prefix(needle)
136 .with_err_msg("could not get refs")?;
137
138 Ok(refs.into_iter().find(|r| r.name == needle))
139 }
140
141 pub fn list_refs_prefix<P: Into<String>>(&self, prefix: P) -> Result<Vec<Reference>, Error> {
147 let cmd = LsRefs {
148 peel: true,
149 symrefs: true,
150 unborn: true,
151 ref_prefix: Some(prefix.into()),
152 hash_kind: self.hash_kind,
153 };
154
155 cmd.execute(self)
156 }
157
158 pub fn objects(&self) -> impl Iterator<Item = &Object> {
159 self.object_cache.values()
160 }
161}
162
163mod discover;
164mod fetch;
165mod get;
166
167fn parse_response(res: Response<Body>) -> Result<ParcelDecoder<BodyReader<'static>>, Error> {
168 let status = res.status();
169
170 if let Some(len) = res.headers().get("content-length")
171 && len == "0"
172 {
173 bail!("server returned empty body (status {})", status);
174 }
175
176 let body = res.into_body();
177 let mut reader = body.into_reader();
178
179 if status != StatusCode::OK {
180 let mut body = Vec::new();
181 reader
182 .read_to_end(&mut body)
183 .with_err_msg("could not read HTTP response")?;
184
185 if body.is_empty() {
186 bail!("HTTP remote error: {status}");
187 }
188
189 if let Ok(err) = str::from_utf8(&body) {
190 bail!("HTTP remote error: {status}: {err}")
191 }
192
193 bail!("HTTP remote error: {status} ({} bytes)", body.len());
194 }
195
196 Ok(ParcelDecoder::new(reader))
197}