|
|
|
@ -2,6 +2,7 @@ use crate::error::unexpected::{self, Mappable}; |
|
|
|
|
use crate::{origin, util}; |
|
|
|
|
use std::{collections, sync}; |
|
|
|
|
|
|
|
|
|
#[derive(Clone)] |
|
|
|
|
struct DescrState { |
|
|
|
|
current_tree: gix_hash::ObjectId, |
|
|
|
|
} |
|
|
|
@ -97,39 +98,78 @@ impl Proxy { |
|
|
|
|
Err(origin::SyncError::InvalidBranchName) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fn get_commit_tree( |
|
|
|
|
async fn get_object( |
|
|
|
|
&self, |
|
|
|
|
descr: &origin::Descr, |
|
|
|
|
commit_hash: &gix_hash::ObjectId, |
|
|
|
|
) -> Result<gix_hash::ObjectId, origin::SyncError> { |
|
|
|
|
let hex = commit_hash.to_string(); |
|
|
|
|
oid: &gix_hash::ObjectId, |
|
|
|
|
) -> unexpected::Result<Option<reqwest::Response>> { |
|
|
|
|
let hex = oid.to_string(); |
|
|
|
|
let (url, _) = Self::deconstruct_descr(descr); |
|
|
|
|
|
|
|
|
|
let commit_object_url = Self::construct_url( |
|
|
|
|
let object_url = Self::construct_url( |
|
|
|
|
url, |
|
|
|
|
format!("/objects/{}/{}", &hex[..2], &hex[2..]).as_str(), |
|
|
|
|
) |
|
|
|
|
.or_unexpected_while("constructing refs url")?; |
|
|
|
|
|
|
|
|
|
let commit_object_bytes = self |
|
|
|
|
Ok(self |
|
|
|
|
.client |
|
|
|
|
.get(commit_object_url) |
|
|
|
|
.get(object_url) |
|
|
|
|
.send() |
|
|
|
|
.await |
|
|
|
|
.or(Err(origin::SyncError::InvalidURL))? |
|
|
|
|
.or_unexpected_while("performing request")? |
|
|
|
|
.error_for_status() |
|
|
|
|
.or(Err(origin::SyncError::InvalidURL))? |
|
|
|
|
.ok()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fn get_commit_tree( |
|
|
|
|
&self, |
|
|
|
|
descr: &origin::Descr, |
|
|
|
|
commit_hash: &gix_hash::ObjectId, |
|
|
|
|
) -> Result<gix_hash::ObjectId, origin::SyncError> { |
|
|
|
|
let commit_object_bytes = self |
|
|
|
|
.get_object(descr, commit_hash) |
|
|
|
|
.await? |
|
|
|
|
.ok_or(origin::SyncError::Unavailable)? |
|
|
|
|
.bytes() |
|
|
|
|
.await |
|
|
|
|
.or(Err(origin::SyncError::InvalidURL))?; |
|
|
|
|
.or(Err(origin::SyncError::Unavailable))?; |
|
|
|
|
|
|
|
|
|
let commit_object = gix_object::ObjectRef::from_loose(commit_object_bytes.as_ref()) |
|
|
|
|
.or(Err(origin::SyncError::InvalidURL))? |
|
|
|
|
.or(Err(origin::SyncError::Unavailable))? |
|
|
|
|
.into_commit() |
|
|
|
|
.ok_or(origin::SyncError::InvalidURL)?; |
|
|
|
|
.ok_or(origin::SyncError::Unavailable)?; |
|
|
|
|
|
|
|
|
|
Ok(commit_object.tree()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fn get_tree_entry( |
|
|
|
|
&self, |
|
|
|
|
descr: &origin::Descr, |
|
|
|
|
tree_hash: &gix_hash::ObjectId, |
|
|
|
|
entry_name: &str, |
|
|
|
|
) -> Result<gix_object::tree::Entry, origin::GetFileError> { |
|
|
|
|
let tree_object_bytes = self |
|
|
|
|
.get_object(descr, tree_hash) |
|
|
|
|
.await? |
|
|
|
|
.ok_or(origin::GetFileError::Unavailable)? |
|
|
|
|
.bytes() |
|
|
|
|
.await |
|
|
|
|
.or(Err(origin::GetFileError::Unavailable))?; |
|
|
|
|
|
|
|
|
|
let tree_object = gix_object::ObjectRef::from_loose(tree_object_bytes.as_ref()) |
|
|
|
|
.or(Err(origin::GetFileError::Unavailable))? |
|
|
|
|
.into_tree() |
|
|
|
|
.ok_or(origin::GetFileError::Unavailable)?; |
|
|
|
|
|
|
|
|
|
for entry in tree_object.entries { |
|
|
|
|
if entry.filename == entry_name { |
|
|
|
|
return Ok(entry.into()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Err(origin::GetFileError::FileNotFound) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl origin::Store for Proxy { |
|
|
|
@ -170,9 +210,97 @@ impl origin::Store for Proxy { |
|
|
|
|
|
|
|
|
|
fn get_file( |
|
|
|
|
&self, |
|
|
|
|
_descr: &origin::Descr, |
|
|
|
|
_path: &str, |
|
|
|
|
) -> Result<util::BoxByteStream, origin::GetFileError> { |
|
|
|
|
panic!("TODO") |
|
|
|
|
descr: &origin::Descr, |
|
|
|
|
path: &str, |
|
|
|
|
) -> util::BoxFuture<'_, Result<util::BoxByteStream, origin::GetFileError>> { |
|
|
|
|
let descr = descr.clone(); |
|
|
|
|
let path = path.to_string(); |
|
|
|
|
Box::pin(async move { |
|
|
|
|
let current_state = self |
|
|
|
|
.state |
|
|
|
|
.read() |
|
|
|
|
.unwrap() |
|
|
|
|
.get(&descr) |
|
|
|
|
.ok_or(origin::GetFileError::DescrNotSynced)? |
|
|
|
|
.clone(); |
|
|
|
|
|
|
|
|
|
let path = path |
|
|
|
|
.as_str() |
|
|
|
|
.parse::<std::path::PathBuf>() |
|
|
|
|
.or_unexpected_while("parsing path")? |
|
|
|
|
.canonicalize() |
|
|
|
|
.or_unexpected_while("canonicalizing path")?; |
|
|
|
|
|
|
|
|
|
let path_parts = path.iter().collect::<Vec<&std::ffi::OsStr>>(); |
|
|
|
|
|
|
|
|
|
let path_parts_len = path_parts.len(); |
|
|
|
|
|
|
|
|
|
if path_parts_len < 2 { |
|
|
|
|
return Err(unexpected::Error::from("path has fewer than 2 parts").into()); |
|
|
|
|
} else if path_parts[0] != std::path::MAIN_SEPARATOR_STR { |
|
|
|
|
return Err(unexpected::Error::from(format!( |
|
|
|
|
"expected first path part to be separator, found {:?}", |
|
|
|
|
path_parts[0] |
|
|
|
|
)) |
|
|
|
|
.into()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let mut tree_hash = current_state.current_tree; |
|
|
|
|
|
|
|
|
|
// The first part is "/" (main separator), and the last is the file name itself.
|
|
|
|
|
// Everything in between (if any) should be directories, so navigate those.
|
|
|
|
|
for dir_name in path_parts[1..path_parts_len - 1].iter() { |
|
|
|
|
let entry = self |
|
|
|
|
.get_tree_entry( |
|
|
|
|
&descr, |
|
|
|
|
&tree_hash, |
|
|
|
|
dir_name |
|
|
|
|
.to_str() |
|
|
|
|
.map_unexpected_while(|| format!("decoding dir name {dir_name:?}"))?, |
|
|
|
|
) |
|
|
|
|
.await?; |
|
|
|
|
|
|
|
|
|
if !entry.mode.is_tree() { |
|
|
|
|
return Err(origin::GetFileError::FileNotFound); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
tree_hash = entry.oid; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let file_name = { |
|
|
|
|
let file_name = path_parts[path_parts_len - 1]; |
|
|
|
|
file_name |
|
|
|
|
.to_str() |
|
|
|
|
.map_unexpected_while(|| format!("decoding file name {file_name:?}"))? |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
let entry = self.get_tree_entry(&descr, &tree_hash, file_name).await?; |
|
|
|
|
|
|
|
|
|
// TODO handle symlinks
|
|
|
|
|
if entry.mode.is_tree() { |
|
|
|
|
return Err(origin::GetFileError::PathIsDirectory); |
|
|
|
|
} else if !entry.mode.is_blob() { |
|
|
|
|
return Err(unexpected::Error::from(format!( |
|
|
|
|
"can't handle entry {} of mode {}", |
|
|
|
|
entry.filename, |
|
|
|
|
entry.mode.as_str() |
|
|
|
|
)) |
|
|
|
|
.into()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let res = self |
|
|
|
|
.get_object(&descr, &entry.oid) |
|
|
|
|
.await? |
|
|
|
|
.map_unexpected_while(|| format!("object for entry {:?} not found", entry))? |
|
|
|
|
.bytes_stream(); |
|
|
|
|
|
|
|
|
|
use futures::StreamExt; |
|
|
|
|
Ok(util::into_box_byte_stream(res.map(|r| { |
|
|
|
|
use std::io::{Error, ErrorKind}; |
|
|
|
|
r.map_err(|e| Error::new(ErrorKind::ConnectionAborted, e)) |
|
|
|
|
}))) |
|
|
|
|
|
|
|
|
|
// TODO this is still not correct, as it will include the git object header
|
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|