use std::str::FromStr;
use std::{collections::HashMap, io::Read};
use ureq::Body;
use ureq::http::Response;
use ureq::{BodyReader, http::StatusCode};
use crate::error::{Error, WithError as _, bail};
use crate::object::Object;
use crate::parcel::{Parcel, ParcelDecoder};
use crate::{Execute as _, HashKind, LsRefs, Reference};
pub struct Repo {
url: String,
hash_kind: HashKind,
object_cache: HashMap<String, Object>,
}
impl Repo {
pub fn new_discover<S: Into<String>>(url: S) -> Result<Self, Error> {
let mut repo = Self {
url: url.into(),
hash_kind: HashKind::Sha1,
object_cache: HashMap::new(),
};
let res = repo.discover().with_err_msg("could not discover")?;
if let Some(format) = &res.object_format {
repo.hash_kind = HashKind::from_str(format)?;
} else {
bail!("remote repo did not send object-format");
}
Ok(repo)
}
pub fn new<S: Into<String>>(url: S, hash_kind: HashKind) -> Self {
Self {
url: url.into(),
hash_kind,
object_cache: HashMap::new(),
}
}
pub fn call<C: Into<Parcel>>(&self, cmd: C) -> Result<Parcel, Error> {
let dec = self.stream(cmd)?;
dec.collect()
}
pub fn stream<C: Into<Parcel>>(
&self,
cmd: C,
) -> Result<ParcelDecoder<BodyReader<'static>>, Error> {
let body: Parcel = cmd.into();
let mut data: Vec<u8> = vec![];
body.write_to(&mut data)
.with_err_msg("could not serialize parcel")?;
let agent = ureq::Agent::config_builder()
.http_status_as_error(false)
.build()
.new_agent();
let res = agent
.post(format!("{}/git-upload-pack", self.url))
.header("Git-Protocol", "version=2")
.header("Content-Type", "application/x-git-upload-pack-request")
.header("Accept", "application/x-git-upload-pack-result")
.send(data)
.with_err_msg("could not send HTTP request")?;
parse_response(res)
}
pub fn list_refs(&self) -> Result<Vec<Reference>, Error> {
let cmd = LsRefs {
peel: true,
symrefs: true,
unborn: true,
ref_prefix: None,
hash_kind: self.hash_kind,
};
cmd.execute(self)
}
pub fn get_ref(&self, needle: &str) -> Result<Option<Reference>, Error> {
let refs = self
.list_refs_prefix(needle)
.with_err_msg("could not get refs")?;
Ok(refs.into_iter().find(|r| r.name == needle))
}
pub fn list_refs_prefix<P: Into<String>>(&self, prefix: P) -> Result<Vec<Reference>, Error> {
let cmd = LsRefs {
peel: true,
symrefs: true,
unborn: true,
ref_prefix: Some(prefix.into()),
hash_kind: self.hash_kind,
};
cmd.execute(self)
}
pub fn objects(&self) -> impl Iterator<Item = &Object> {
self.object_cache.values()
}
}
mod discover;
mod fetch;
mod get;
fn parse_response(res: Response<Body>) -> Result<ParcelDecoder<BodyReader<'static>>, Error> {
let status = res.status();
if let Some(len) = res.headers().get("content-length")
&& len == "0"
{
bail!("server returned empty body (status {})", status);
}
let body = res.into_body();
let mut reader = body.into_reader();
if status != StatusCode::OK {
let mut body = Vec::new();
reader
.read_to_end(&mut body)
.with_err_msg("could not read HTTP response")?;
if body.is_empty() {
bail!("HTTP remote error: {status}");
}
if let Ok(err) = str::from_utf8(&body) {
bail!("HTTP remote error: {status}: {err}")
}
bail!("HTTP remote error: {status} ({} bytes)", body.len());
}
Ok(ParcelDecoder::new(reader))
}