Skip to content

Commit

Permalink
change!: Use the gix-object::Find trait
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Nov 4, 2023
1 parent 1165de0 commit bf1e688
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 109 deletions.
6 changes: 4 additions & 2 deletions gix-worktree-stream/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ use crate::{protocol, Entry, Stream};
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("Could not find a blob or tree for archival")]
Find(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("Could not find a tree's leaf, typically a blob")]
Find(#[from] gix_object::find::existing::Error),
#[error("Could not find a tree to traverse")]
FindTree(#[from] gix_object::find::existing_iter::Error),
#[error("Could not query attributes for path \"{path}\"")]
Attributes {
path: BString,
Expand Down
43 changes: 17 additions & 26 deletions gix-worktree-stream/src/from_tree/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::io::Write;

use gix_object::bstr::BStr;
use gix_object::FindExt;

use crate::{entry, entry::Error, protocol, AdditionalEntry, SharedErrorSlot, Stream};

/// Use `find` to traverse `tree` and fetch the contained blobs to return as [`Stream`], which makes them queryable
/// Use `objects` to traverse `tree` and fetch the contained blobs to return as [`Stream`], which makes them queryable
/// on demand with support for streaming each entry.
///
/// `pipeline` is used to convert blobs to their worktree representation, and `attributes` is used to read
Expand Down Expand Up @@ -32,26 +33,25 @@ use crate::{entry, entry::Error, protocol, AdditionalEntry, SharedErrorSlot, Str
/// ### Limitations
///
/// * `export-subst` is not support, as it requires the entire formatting engine of `git log`.
pub fn from_tree<Find, E1, E2>(
pub fn from_tree<Find, E>(
tree: gix_hash::ObjectId,
find: Find,
objects: Find,
pipeline: gix_filter::Pipeline,
attributes: impl FnMut(&BStr, gix_object::tree::EntryMode, &mut gix_attributes::search::Outcome) -> Result<(), E2>
attributes: impl FnMut(&BStr, gix_object::tree::EntryMode, &mut gix_attributes::search::Outcome) -> Result<(), E>
+ Send
+ 'static,
) -> Stream
where
Find: for<'a> FnMut(&gix_hash::oid, &'a mut Vec<u8>) -> Result<gix_object::Data<'a>, E1> + Clone + Send + 'static,
E1: std::error::Error + Send + Sync + 'static,
E2: std::error::Error + Send + Sync + 'static,
Find: gix_object::Find + Clone + Send + 'static,
E: std::error::Error + Send + Sync + 'static,
{
let (stream, mut write, additional_entries) = Stream::new();
std::thread::spawn({
let slot = stream.err.clone();
move || {
if let Err(err) = run(
tree,
find,
objects,
pipeline,
attributes,
&mut write,
Expand All @@ -76,28 +76,26 @@ where
stream
}

fn run<Find, E1, E2>(
fn run<Find, E>(
tree: gix_hash::ObjectId,
mut find: Find,
objects: Find,
mut pipeline: gix_filter::Pipeline,
mut attributes: impl FnMut(&BStr, gix_object::tree::EntryMode, &mut gix_attributes::search::Outcome) -> Result<(), E2>
mut attributes: impl FnMut(&BStr, gix_object::tree::EntryMode, &mut gix_attributes::search::Outcome) -> Result<(), E>
+ Send
+ 'static,
out: &mut gix_features::io::pipe::Writer,
err: SharedErrorSlot,
additional_entries: std::sync::mpsc::Receiver<AdditionalEntry>,
) -> Result<(), Error>
where
Find: for<'a> FnMut(&gix_hash::oid, &'a mut Vec<u8>) -> Result<gix_object::Data<'a>, E1> + Clone + Send + 'static,
E1: std::error::Error + Send + Sync + 'static,
E2: std::error::Error + Send + Sync + 'static,
Find: gix_object::Find + Clone,
E: std::error::Error + Send + Sync + 'static,
{
let mut buf = Vec::new();
let obj = find(tree.as_ref(), &mut buf).map_err(|err| Error::Find(Box::new(err)))?;
let tree_iter = objects.find_tree_iter(tree.as_ref(), &mut buf)?;
if pipeline.driver_context_mut().treeish.is_none() {
pipeline.driver_context_mut().treeish = Some(tree);
}
let tree = gix_object::TreeRefIter::from_bytes(obj.data);

let mut attrs = gix_attributes::search::Outcome::default();
attrs.initialize_with_selection(&Default::default(), Some("export-ignore"));
Expand All @@ -106,10 +104,7 @@ where
err,
pipeline,
attrs,
find: {
let mut find = find.clone();
move |a: &gix_hash::oid, b: &mut Vec<u8>| find(a, b).map_err(|err| Error::Find(Box::new(err)))
},
objects: objects.clone(),
fetch_attributes: move |a: &BStr, b: gix_object::tree::EntryMode, c: &mut gix_attributes::search::Outcome| {
attributes(a, b, c).map_err(|err| Error::Attributes {
source: Box::new(err),
Expand All @@ -121,13 +116,9 @@ where
buf: Vec::with_capacity(1024),
};
gix_traverse::tree::breadthfirst(
tree,
tree_iter,
gix_traverse::tree::breadthfirst::State::default(),
|id, buf| {
find(id, buf)
.map(|obj| gix_object::TreeRefIter::from_bytes(obj.data))
.ok()
},
&objects,
&mut dlg,
)?;

Expand Down
18 changes: 9 additions & 9 deletions gix-worktree-stream/src/from_tree/traverse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ use std::{collections::VecDeque, io::Write};
use gix_filter::{driver::apply::MaybeDelayed, pipeline::convert::ToWorktreeOutcome};
use gix_object::{
bstr::{BStr, BString, ByteSlice, ByteVec},
tree,
tree, FindExt,
};
use gix_traverse::tree::{visit::Action, Visit};

use crate::{entry::Error, protocol, SharedErrorSlot};

pub struct Delegate<'a, AttributesFn, FindFn>
pub struct Delegate<'a, AttributesFn, Find>
where
FindFn: for<'b> FnMut(&gix_hash::oid, &'b mut Vec<u8>) -> Result<gix_object::Data<'b>, Error> + 'static,
Find: gix_object::Find,
{
pub(crate) out: &'a mut gix_features::io::pipe::Writer,
pub(crate) err: SharedErrorSlot,
Expand All @@ -20,13 +20,13 @@ where
pub(crate) pipeline: gix_filter::Pipeline,
pub(crate) attrs: gix_attributes::search::Outcome,
pub(crate) fetch_attributes: AttributesFn,
pub(crate) find: FindFn,
pub(crate) objects: Find,
pub(crate) buf: Vec<u8>,
}

impl<AttributesFn, FindFn> Delegate<'_, AttributesFn, FindFn>
impl<AttributesFn, Find> Delegate<'_, AttributesFn, Find>
where
FindFn: for<'b> FnMut(&gix_hash::oid, &'b mut Vec<u8>) -> Result<gix_object::Data<'b>, Error> + 'static,
Find: gix_object::Find,
AttributesFn:
FnMut(&BStr, gix_object::tree::EntryMode, &mut gix_attributes::search::Outcome) -> Result<(), Error> + 'static,
{
Expand Down Expand Up @@ -62,7 +62,7 @@ where
if self.ignore_state().is_set() {
return Ok(Action::Continue);
}
(self.find)(entry.oid, &mut self.buf)?;
self.objects.find(entry.oid, &mut self.buf)?;

self.pipeline.driver_context_mut().blob = Some(entry.oid.into());
let converted = self.pipeline.convert_to_worktree(
Expand Down Expand Up @@ -99,9 +99,9 @@ where
}
}

impl<AttributesFn, FindFn> Visit for Delegate<'_, AttributesFn, FindFn>
impl<AttributesFn, Find> Visit for Delegate<'_, AttributesFn, Find>
where
FindFn: for<'a> FnMut(&gix_hash::oid, &'a mut Vec<u8>) -> Result<gix_object::Data<'a>, Error> + 'static,
Find: gix_object::Find,
AttributesFn:
FnMut(&BStr, gix_object::tree::EntryMode, &mut gix_attributes::search::Outcome) -> Result<(), Error> + 'static,
{
Expand Down
39 changes: 22 additions & 17 deletions gix-worktree-stream/tests/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,45 @@ mod from_tree {
};

use gix_attributes::glob::pattern::Case;
use gix_object::FindExt;
use gix_hash::oid;
use gix_object::{bstr::ByteSlice, tree::EntryMode};
use gix_object::{Data, FindExt};
use gix_testtools::once_cell::sync::Lazy;
use gix_worktree::stack::state::attributes::Source;

use crate::hex_to_id;

#[derive(Clone)]
struct FailObjectRetrieval;

impl gix_object::Find for FailObjectRetrieval {
fn try_find<'a>(
&self,
_id: &oid,
_buffer: &'a mut Vec<u8>,
) -> Result<Option<Data<'a>>, gix_object::find::Error> {
Err(Box::new(Error::new(ErrorKind::Other, "object retrieval failed")))
}
}

#[test]
fn can_receive_err_if_root_is_not_found() {
let mut stream = gix_worktree_stream::from_tree(
gix_hash::Kind::Sha1.null(),
|_, _| Err(Error::new(ErrorKind::Other, "object retrieval failed")),
FailObjectRetrieval,
mutating_pipeline(false),
|_, _, _| -> Result<_, Infallible> { unreachable!("must not be called") },
);
let err = stream.next_entry().unwrap_err();
assert_eq!(err.to_string(), "Could not find a blob or tree for archival");
assert_eq!(err.to_string(), "Could not find a tree to traverse");
}

#[test]
fn can_receive_err_if_attribute_not_found() -> gix_testtools::Result {
let (_dir, head_tree, odb, _cache) = basic()?;
let mut stream = gix_worktree_stream::from_tree(
head_tree,
move |id, buf| odb.find(id, buf),
mutating_pipeline(false),
|_, _, _| Err(Error::new(ErrorKind::Other, "attribute retrieval failed")),
);
let mut stream = gix_worktree_stream::from_tree(head_tree, odb, mutating_pipeline(false), |_, _, _| {
Err(Error::new(ErrorKind::Other, "attribute retrieval failed"))
});
let err = stream.next_entry().unwrap_err();
assert_eq!(
err.to_string(),
Expand All @@ -53,10 +64,7 @@ mod from_tree {
let (dir, head_tree, odb, mut cache) = basic()?;
let mut stream = gix_worktree_stream::from_tree(
head_tree,
{
let odb = odb.clone();
move |id, buf| odb.find(id, buf)
},
odb.clone(),
mutating_pipeline(true),
move |rela_path, mode, attrs| {
cache
Expand Down Expand Up @@ -214,10 +222,7 @@ mod from_tree {
let (_dir, head_tree, odb, mut cache) = basic()?;
let mut stream = gix_worktree_stream::from_tree(
head_tree,
{
let odb = odb.clone();
move |id, buf| odb.find(id, buf)
},
odb.clone(),
mutating_pipeline(false),
move |rela_path, mode, attrs| {
cache
Expand Down
16 changes: 5 additions & 11 deletions gix-worktree/src/stack/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,13 @@ pub struct Statistics {
pub pop_directory: usize,
}

pub(crate) type FindFn<'a> = dyn for<'b> FnMut(
&gix_hash::oid,
&'b mut Vec<u8>,
) -> Result<gix_object::BlobRef<'b>, Box<dyn std::error::Error + Send + Sync>>
+ 'a;

pub(crate) struct StackDelegate<'a, 'find> {
pub state: &'a mut State,
pub buf: &'a mut Vec<u8>,
#[cfg_attr(not(feature = "attributes"), allow(dead_code))]
pub is_dir: bool,
pub id_mappings: &'a Vec<PathIdMapping>,
pub find: &'find mut FindFn<'find>,
pub objects: &'find dyn gix_object::Find,
pub case: gix_glob::pattern::Case,
pub statistics: &'a mut super::Statistics,
}
Expand Down Expand Up @@ -63,7 +57,7 @@ impl<'a, 'find> gix_fs::stack::Delegate for StackDelegate<'a, 'find> {
rela_dir,
self.buf,
self.id_mappings,
self.find,
self.objects,
&mut self.statistics.attributes,
)?;
}
Expand All @@ -75,7 +69,7 @@ impl<'a, 'find> gix_fs::stack::Delegate for StackDelegate<'a, 'find> {
rela_dir,
self.buf,
self.id_mappings,
&mut self.find,
self.objects,
&mut self.statistics.attributes,
)?;
ignore.push_directory(
Expand All @@ -84,7 +78,7 @@ impl<'a, 'find> gix_fs::stack::Delegate for StackDelegate<'a, 'find> {
rela_dir,
self.buf,
self.id_mappings,
&mut self.find,
self.objects,
self.case,
&mut self.statistics.ignore,
)?
Expand All @@ -95,7 +89,7 @@ impl<'a, 'find> gix_fs::stack::Delegate for StackDelegate<'a, 'find> {
rela_dir,
self.buf,
self.id_mappings,
&mut self.find,
self.objects,
self.case,
&mut self.statistics.ignore,
)?,
Expand Down
23 changes: 10 additions & 13 deletions gix-worktree/src/stack/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
use std::path::{Path, PathBuf};

use bstr::{BStr, ByteSlice};
use gix_hash::oid;

use super::Stack;
use crate::PathIdMapping;
Expand Down Expand Up @@ -106,26 +105,25 @@ impl Stack {
/// symlinks are in that path.
/// Unless `is_dir` is known with `Some(…)`, then `relative` points to a directory itself in which case the entire resulting
/// path is created as directory. If it's not known it is assumed to be a file.
/// `find` maybe used to lookup objects from an [id mapping][crate::stack::State::id_mappings_from_index()], with mappnigs
/// `objects` maybe used to lookup objects from an [id mapping][crate::stack::State::id_mappings_from_index()], with mappnigs
///
/// Provide access to cached information for that `relative` path via the returned platform.
pub fn at_path<Find, E>(
pub fn at_path<Find>(
&mut self,
relative: impl AsRef<Path>,
is_dir: Option<bool>,
mut find: Find,
objects: Find,
) -> std::io::Result<Platform<'_>>
where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<gix_object::BlobRef<'a>, E>,
E: std::error::Error + Send + Sync + 'static,
Find: gix_object::Find,
{
self.statistics.platforms += 1;
let mut delegate = StackDelegate {
state: &mut self.state,
buf: &mut self.buf,
is_dir: is_dir.unwrap_or(false),
id_mappings: &self.id_mappings,
find: &mut |oid, buf| Ok(find(oid, buf).map_err(Box::new)?),
objects: &objects,
case: self.case,
statistics: &mut self.statistics,
};
Expand All @@ -136,31 +134,30 @@ impl Stack {

/// Obtain a platform for lookups from a repo-`relative` path, typically obtained from an index entry. `is_dir` should reflect
/// whether it's a directory or not, or left at `None` if unknown.
/// `find` maybe used to lookup objects from an [id mapping][crate::stack::State::id_mappings_from_index()].
/// `objects` maybe used to lookup objects from an [id mapping][crate::stack::State::id_mappings_from_index()].
/// All effects are similar to [`at_path()`][Self::at_path()].
///
/// If `relative` ends with `/` and `is_dir` is `None`, it is automatically assumed to be a directory.
///
/// ### Panics
///
/// on illformed UTF8 in `relative`
pub fn at_entry<'r, Find, E>(
pub fn at_entry<'r, Find>(
&mut self,
relative: impl Into<&'r BStr>,
is_dir: Option<bool>,
find: Find,
objects: Find,
) -> std::io::Result<Platform<'_>>
where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Result<gix_object::BlobRef<'a>, E>,
E: std::error::Error + Send + Sync + 'static,
Find: gix_object::Find,
{
let relative = relative.into();
let relative_path = gix_path::from_bstr(relative);

self.at_path(
relative_path,
is_dir.or_else(|| relative.ends_with_str("/").then_some(true)),
find,
objects,
)
}
}
Expand Down
Loading

0 comments on commit bf1e688

Please sign in to comment.