use std::collections::BTreeMap;
use std::fmt;
use std::iter;
use std::pin::Pin;
use std::sync::Arc;
use std::task::Context;
use std::task::Poll;
use std::task::ready;
use std::vec;
use either::Either;
use futures::Stream;
use futures::StreamExt as _;
use futures::future::BoxFuture;
use futures::future::try_join;
use futures::stream::BoxStream;
use itertools::EitherOrBoth;
use itertools::Itertools as _;
use pollster::FutureExt as _;
use crate::backend::BackendResult;
use crate::backend::CopyId;
use crate::backend::TreeId;
use crate::backend::TreeValue;
use crate::conflict_labels::ConflictLabels;
use crate::copies::CopiesTreeDiffEntry;
use crate::copies::CopiesTreeDiffStream;
use crate::copies::CopyHistoryDiffStream;
use crate::copies::CopyHistoryTreeDiffEntry;
use crate::copies::CopyRecords;
use crate::matchers::EverythingMatcher;
use crate::matchers::Matcher;
use crate::merge::Diff;
use crate::merge::Merge;
use crate::merge::MergeBuilder;
use crate::merge::MergedTreeVal;
use crate::merge::MergedTreeValue;
use crate::repo_path::RepoPath;
use crate::repo_path::RepoPathBuf;
use crate::repo_path::RepoPathComponent;
use crate::store::Store;
use crate::tree::Tree;
use crate::tree_merge::merge_trees;
#[derive(Clone)]
pub struct MergedTree {
store: Arc<Store>,
tree_ids: Merge<TreeId>,
labels: ConflictLabels,
}
impl fmt::Debug for MergedTree {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MergedTree")
.field("tree_ids", &self.tree_ids)
.field("labels", &self.labels)
.finish_non_exhaustive()
}
}
impl MergedTree {
pub fn resolved(store: Arc<Store>, tree_id: TreeId) -> Self {
Self {
store,
tree_ids: Merge::resolved(tree_id),
labels: ConflictLabels::unlabeled(),
}
}
pub fn new(store: Arc<Store>, tree_ids: Merge<TreeId>, labels: ConflictLabels) -> Self {
if let Some(num_sides) = labels.num_sides() {
assert_eq!(tree_ids.num_sides(), num_sides);
}
Self {
store,
tree_ids,
labels,
}
}
pub fn store(&self) -> &Arc<Store> {
&self.store
}
pub fn tree_ids(&self) -> &Merge<TreeId> {
&self.tree_ids
}
pub fn into_tree_ids(self) -> Merge<TreeId> {
self.tree_ids
}
pub fn labels(&self) -> &ConflictLabels {
&self.labels
}
pub fn tree_ids_and_labels(&self) -> (&Merge<TreeId>, &ConflictLabels) {
(&self.tree_ids, &self.labels)
}
pub fn into_tree_ids_and_labels(self) -> (Merge<TreeId>, ConflictLabels) {
(self.tree_ids, self.labels)
}
pub async fn trees(&self) -> BackendResult<Merge<Tree>> {
self.tree_ids
.try_map_async(|id| self.store.get_tree(RepoPathBuf::root(), id))
.await
}
pub fn labels_by_term<'a>(&'a self, label: &'a str) -> Merge<&'a str> {
if self.tree_ids.is_resolved() {
assert!(!self.labels.has_labels());
Merge::resolved(label)
} else if self.labels.has_labels() {
let labels = self.labels.as_merge();
assert_eq!(labels.num_sides(), self.tree_ids.num_sides());
labels.map(|label| label.as_str())
} else {
Merge::repeated("", self.tree_ids.num_sides())
}
}
pub async fn resolve(self) -> BackendResult<Self> {
let merged = merge_trees(&self.store, self.tree_ids).await?;
let (simplified_labels, simplified) = if merged.is_resolved() {
(ConflictLabels::unlabeled(), merged)
} else {
self.labels.simplify_with(&merged)
};
if cfg!(debug_assertions) {
let re_merged = merge_trees(&self.store, simplified.clone()).await.unwrap();
debug_assert_eq!(re_merged, simplified);
}
Ok(Self {
store: self.store,
tree_ids: simplified,
labels: simplified_labels,
})
}
pub fn conflicts(
&self,
) -> impl Iterator<Item = (RepoPathBuf, BackendResult<MergedTreeValue>)> + use<> {
self.conflicts_matching(&EverythingMatcher)
}
pub fn conflicts_matching<'matcher>(
&self,
matcher: &'matcher dyn Matcher,
) -> impl Iterator<Item = (RepoPathBuf, BackendResult<MergedTreeValue>)> + use<'matcher> {
ConflictIterator::new(self, matcher)
}
pub fn has_conflict(&self) -> bool {
!self.tree_ids.is_resolved()
}
pub async fn path_value(&self, path: &RepoPath) -> BackendResult<MergedTreeValue> {
match path.split() {
Some((dir, basename)) => {
let trees = self.trees().await?;
match trees.sub_tree_recursive(dir).await? {
None => Ok(Merge::absent()),
Some(tree) => Ok(tree.value(basename).cloned()),
}
}
None => Ok(self.to_merged_tree_value()),
}
}
pub async fn copy_value(&self, id: &CopyId) -> BackendResult<Option<TreeValue>> {
let copy = self.store().backend().read_copy(id).await?;
let merged_val = self.path_value(©.current_path).await?;
match merged_val.into_resolved() {
Ok(Some(val)) if val.copy_id() == Some(id) => Ok(Some(val)),
_ => Ok(None),
}
}
fn to_merged_tree_value(&self) -> MergedTreeValue {
self.tree_ids
.map(|tree_id| Some(TreeValue::Tree(tree_id.clone())))
}
pub fn entries(&self) -> TreeEntriesIterator<'static> {
self.entries_matching(&EverythingMatcher)
}
pub fn entries_matching<'matcher>(
&self,
matcher: &'matcher dyn Matcher,
) -> TreeEntriesIterator<'matcher> {
TreeEntriesIterator::new(self, matcher)
}
fn diff_stream_internal<'matcher>(
&self,
other: &Self,
matcher: &'matcher dyn Matcher,
) -> TreeDiffStream<'matcher> {
let concurrency = self.store().concurrency();
if concurrency <= 1 {
futures::stream::iter(TreeDiffIterator::new(self, other, matcher)).boxed()
} else {
TreeDiffStreamImpl::new(self, other, matcher, concurrency).boxed()
}
}
pub fn diff_stream<'matcher>(
&self,
other: &Self,
matcher: &'matcher dyn Matcher,
) -> TreeDiffStream<'matcher> {
stream_without_trees(self.diff_stream_internal(other, matcher))
}
pub fn diff_stream_with_trees<'matcher>(
&self,
other: &Self,
matcher: &'matcher dyn Matcher,
) -> TreeDiffStream<'matcher> {
self.diff_stream_internal(other, matcher)
}
pub fn diff_stream_for_file_system<'matcher>(
&self,
other: &Self,
matcher: &'matcher dyn Matcher,
) -> TreeDiffStream<'matcher> {
DiffStreamForFileSystem::new(self.diff_stream_internal(other, matcher)).boxed()
}
pub fn diff_stream_with_copies<'a>(
&self,
other: &Self,
matcher: &'a dyn Matcher,
copy_records: &'a CopyRecords,
) -> BoxStream<'a, CopiesTreeDiffEntry> {
let stream = self.diff_stream(other, matcher);
CopiesTreeDiffStream::new(stream, self.clone(), other.clone(), copy_records).boxed()
}
pub fn diff_stream_with_copy_history<'a>(
&'a self,
other: &'a Self,
matcher: &'a dyn Matcher,
) -> BoxStream<'a, CopyHistoryTreeDiffEntry> {
let stream = self.diff_stream(other, matcher);
CopyHistoryDiffStream::new(stream, self, other).boxed()
}
pub async fn merge(merge: Merge<(Self, String)>) -> BackendResult<Self> {
Self::merge_no_resolve(merge).resolve().await
}
pub fn merge_no_resolve(merge: Merge<(Self, String)>) -> Self {
debug_assert!(
merge
.iter()
.map(|(tree, _)| Arc::as_ptr(tree.store()))
.all_equal()
);
let store = merge.first().0.store().clone();
let flattened_labels = ConflictLabels::from_merge(
merge
.map(|(tree, label)| tree.labels_by_term(label))
.flatten()
.map(|&label| label.to_owned()),
);
let flattened_tree_ids: Merge<TreeId> = merge
.into_map(|(tree, _label)| tree.into_tree_ids())
.flatten();
let (labels, tree_ids) = flattened_labels.simplify_with(&flattened_tree_ids);
Self::new(store, tree_ids, labels)
}
}
#[derive(Debug)]
pub struct TreeDiffEntry {
pub path: RepoPathBuf,
pub values: BackendResult<Diff<MergedTreeValue>>,
}
pub type TreeDiffStream<'matcher> = BoxStream<'matcher, TreeDiffEntry>;
fn all_tree_entries(
trees: &Merge<Tree>,
) -> impl Iterator<Item = (&RepoPathComponent, MergedTreeVal<'_>)> {
if let Some(tree) = trees.as_resolved() {
let iter = tree
.entries_non_recursive()
.map(|entry| (entry.name(), Merge::normal(entry.value())));
Either::Left(iter)
} else {
let same_change = trees.first().store().merge_options().same_change;
let iter = all_merged_tree_entries(trees).map(move |(name, values)| {
let values = match values.resolve_trivial(same_change) {
Some(resolved) => Merge::resolved(*resolved),
None => values,
};
(name, values)
});
Either::Right(iter)
}
}
pub fn all_merged_tree_entries(
trees: &Merge<Tree>,
) -> impl Iterator<Item = (&RepoPathComponent, MergedTreeVal<'_>)> {
let mut entries_iters = trees
.iter()
.map(|tree| tree.entries_non_recursive().peekable())
.collect_vec();
iter::from_fn(move || {
let next_name = entries_iters
.iter_mut()
.filter_map(|iter| iter.peek())
.map(|entry| entry.name())
.min()?;
let values: MergeBuilder<_> = entries_iters
.iter_mut()
.map(|iter| {
let entry = iter.next_if(|entry| entry.name() == next_name)?;
Some(entry.value())
})
.collect();
Some((next_name, values.build()))
})
}
fn merged_tree_entry_diff<'a>(
trees1: &'a Merge<Tree>,
trees2: &'a Merge<Tree>,
) -> impl Iterator<Item = (&'a RepoPathComponent, Diff<MergedTreeVal<'a>>)> {
itertools::merge_join_by(
all_tree_entries(trees1),
all_tree_entries(trees2),
|(name1, _), (name2, _)| name1.cmp(name2),
)
.map(|entry| match entry {
EitherOrBoth::Both((name, value1), (_, value2)) => (name, Diff::new(value1, value2)),
EitherOrBoth::Left((name, value1)) => (name, Diff::new(value1, Merge::absent())),
EitherOrBoth::Right((name, value2)) => (name, Diff::new(Merge::absent(), value2)),
})
.filter(|(_, diff)| diff.is_changed())
}
pub struct TreeEntriesIterator<'matcher> {
store: Arc<Store>,
stack: Vec<TreeEntriesDirItem>,
matcher: &'matcher dyn Matcher,
}
struct TreeEntriesDirItem {
entries: Vec<(RepoPathBuf, MergedTreeValue)>,
}
impl TreeEntriesDirItem {
fn new(trees: &Merge<Tree>, matcher: &dyn Matcher) -> Self {
let mut entries = vec![];
let dir = trees.first().dir();
for (name, value) in all_tree_entries(trees) {
let path = dir.join(name);
if value.is_tree() {
if matcher.visit(&path).is_nothing() {
continue;
}
} else if !matcher.matches(&path) {
continue;
}
entries.push((path, value.cloned()));
}
entries.reverse();
Self { entries }
}
}
impl<'matcher> TreeEntriesIterator<'matcher> {
fn new(trees: &MergedTree, matcher: &'matcher dyn Matcher) -> Self {
Self {
store: trees.store.clone(),
stack: vec![TreeEntriesDirItem {
entries: vec![(RepoPathBuf::root(), trees.to_merged_tree_value())],
}],
matcher,
}
}
}
impl Iterator for TreeEntriesIterator<'_> {
type Item = (RepoPathBuf, BackendResult<MergedTreeValue>);
fn next(&mut self) -> Option<Self::Item> {
while let Some(top) = self.stack.last_mut() {
if let Some((path, value)) = top.entries.pop() {
let maybe_trees = match value.to_tree_merge(&self.store, &path).block_on() {
Ok(maybe_trees) => maybe_trees,
Err(err) => return Some((path, Err(err))),
};
if let Some(trees) = maybe_trees {
self.stack
.push(TreeEntriesDirItem::new(&trees, self.matcher));
} else {
return Some((path, Ok(value)));
}
} else {
self.stack.pop();
}
}
None
}
}
struct ConflictsDirItem {
entries: Vec<(RepoPathBuf, MergedTreeValue)>,
}
impl ConflictsDirItem {
fn new(trees: &Merge<Tree>, matcher: &dyn Matcher) -> Self {
if trees.is_resolved() {
return Self { entries: vec![] };
}
let dir = trees.first().dir();
let mut entries = vec![];
for (basename, value) in all_tree_entries(trees) {
if value.is_resolved() {
continue;
}
let path = dir.join(basename);
if value.is_tree() {
if matcher.visit(&path).is_nothing() {
continue;
}
} else if !matcher.matches(&path) {
continue;
}
entries.push((path, value.cloned()));
}
entries.reverse();
Self { entries }
}
}
struct ConflictIterator<'matcher> {
store: Arc<Store>,
stack: Vec<ConflictsDirItem>,
matcher: &'matcher dyn Matcher,
}
impl<'matcher> ConflictIterator<'matcher> {
fn new(tree: &MergedTree, matcher: &'matcher dyn Matcher) -> Self {
Self {
store: tree.store().clone(),
stack: vec![ConflictsDirItem {
entries: vec![(RepoPathBuf::root(), tree.to_merged_tree_value())],
}],
matcher,
}
}
}
impl Iterator for ConflictIterator<'_> {
type Item = (RepoPathBuf, BackendResult<MergedTreeValue>);
fn next(&mut self) -> Option<Self::Item> {
while let Some(top) = self.stack.last_mut() {
if let Some((path, tree_values)) = top.entries.pop() {
match tree_values.to_tree_merge(&self.store, &path).block_on() {
Ok(Some(trees)) => {
self.stack.push(ConflictsDirItem::new(&trees, self.matcher));
}
Ok(None) => {
return Some((path, Ok(tree_values)));
}
Err(err) => {
return Some((path, Err(err)));
}
}
} else {
self.stack.pop();
}
}
None
}
}
pub struct TreeDiffIterator<'matcher> {
store: Arc<Store>,
stack: Vec<TreeDiffDir>,
matcher: &'matcher dyn Matcher,
}
struct TreeDiffDir {
entries: Vec<(RepoPathBuf, Diff<MergedTreeValue>)>,
}
impl<'matcher> TreeDiffIterator<'matcher> {
pub fn new(tree1: &MergedTree, tree2: &MergedTree, matcher: &'matcher dyn Matcher) -> Self {
assert!(Arc::ptr_eq(tree1.store(), tree2.store()));
let root_dir = RepoPath::root();
let mut stack = Vec::new();
let root_diff = Diff::new(tree1.to_merged_tree_value(), tree2.to_merged_tree_value());
if root_diff.is_changed() && !matcher.visit(root_dir).is_nothing() {
stack.push(TreeDiffDir {
entries: vec![(root_dir.to_owned(), root_diff)],
});
}
Self {
store: tree1.store().clone(),
stack,
matcher,
}
}
fn trees(
store: &Arc<Store>,
dir: &RepoPath,
values: &MergedTreeValue,
) -> BackendResult<Merge<Tree>> {
if let Some(trees) = values.to_tree_merge(store, dir).block_on()? {
Ok(trees)
} else {
Ok(Merge::resolved(Tree::empty(store.clone(), dir.to_owned())))
}
}
}
impl TreeDiffDir {
fn from_trees(
dir: &RepoPath,
trees1: &Merge<Tree>,
trees2: &Merge<Tree>,
matcher: &dyn Matcher,
) -> Self {
let mut entries = vec![];
for (name, diff) in merged_tree_entry_diff(trees1, trees2) {
let path = dir.join(name);
let tree_before = diff.before.is_tree();
let tree_after = diff.after.is_tree();
let tree_matches = (tree_before || tree_after) && !matcher.visit(&path).is_nothing();
let file_matches = (!tree_before || !tree_after) && matcher.matches(&path);
let before = if (tree_before && tree_matches) || (!tree_before && file_matches) {
diff.before
} else {
Merge::absent()
};
let after = if (tree_after && tree_matches) || (!tree_after && file_matches) {
diff.after
} else {
Merge::absent()
};
if before.is_absent() && after.is_absent() {
continue;
}
entries.push((path, Diff::new(before.cloned(), after.cloned())));
}
entries.reverse();
Self { entries }
}
}
impl Iterator for TreeDiffIterator<'_> {
type Item = TreeDiffEntry;
fn next(&mut self) -> Option<Self::Item> {
while let Some(top) = self.stack.last_mut() {
let Some((path, diff)) = top.entries.pop() else {
self.stack.pop().unwrap();
continue;
};
if diff.before.is_tree() || diff.after.is_tree() {
let (before_tree, after_tree) = match (
Self::trees(&self.store, &path, &diff.before),
Self::trees(&self.store, &path, &diff.after),
) {
(Ok(before_tree), Ok(after_tree)) => (before_tree, after_tree),
(Err(before_err), _) => {
return Some(TreeDiffEntry {
path,
values: Err(before_err),
});
}
(_, Err(after_err)) => {
return Some(TreeDiffEntry {
path,
values: Err(after_err),
});
}
};
let subdir =
TreeDiffDir::from_trees(&path, &before_tree, &after_tree, self.matcher);
self.stack.push(subdir);
}
if diff.before.is_file_like()
|| diff.after.is_file_like()
|| self.matcher.matches(&path)
{
return Some(TreeDiffEntry {
path,
values: Ok(diff),
});
}
}
None
}
}
pub struct TreeDiffStreamImpl<'matcher> {
store: Arc<Store>,
matcher: &'matcher dyn Matcher,
items: BTreeMap<RepoPathBuf, BackendResult<Diff<MergedTreeValue>>>,
#[expect(clippy::type_complexity)]
pending_trees:
BTreeMap<RepoPathBuf, BoxFuture<'matcher, BackendResult<(Merge<Tree>, Merge<Tree>)>>>,
max_concurrent_reads: usize,
max_queued_items: usize,
}
impl<'matcher> TreeDiffStreamImpl<'matcher> {
pub fn new(
tree1: &MergedTree,
tree2: &MergedTree,
matcher: &'matcher dyn Matcher,
max_concurrent_reads: usize,
) -> Self {
assert!(Arc::ptr_eq(tree1.store(), tree2.store()));
let store = tree1.store().clone();
let mut stream = Self {
store: store.clone(),
matcher,
items: BTreeMap::new(),
pending_trees: BTreeMap::new(),
max_concurrent_reads,
max_queued_items: 10000,
};
let dir = RepoPathBuf::root();
let merged_tree1 = tree1.to_merged_tree_value();
let merged_tree2 = tree2.to_merged_tree_value();
let root_diff = Diff::new(merged_tree1.clone(), merged_tree2.clone());
if root_diff.is_changed() && matcher.matches(&dir) {
stream.items.insert(dir.clone(), Ok(root_diff));
}
let root_tree_fut = Box::pin(try_join(
Self::trees(store.clone(), dir.clone(), merged_tree1),
Self::trees(store, dir.clone(), merged_tree2),
));
stream.pending_trees.insert(dir, root_tree_fut);
stream
}
async fn single_tree(
store: &Arc<Store>,
dir: RepoPathBuf,
value: Option<&TreeValue>,
) -> BackendResult<Tree> {
match value {
Some(TreeValue::Tree(tree_id)) => store.get_tree(dir, tree_id).await,
_ => Ok(Tree::empty(store.clone(), dir.clone())),
}
}
async fn trees(
store: Arc<Store>,
dir: RepoPathBuf,
values: MergedTreeValue,
) -> BackendResult<Merge<Tree>> {
if values.is_tree() {
values
.try_map_async(|value| Self::single_tree(&store, dir.clone(), value.as_ref()))
.await
} else {
Ok(Merge::resolved(Tree::empty(store, dir)))
}
}
fn add_dir_diff_items(&mut self, dir: &RepoPath, trees1: &Merge<Tree>, trees2: &Merge<Tree>) {
for (basename, diff) in merged_tree_entry_diff(trees1, trees2) {
let path = dir.join(basename);
let tree_before = diff.before.is_tree();
let tree_after = diff.after.is_tree();
let tree_matches =
(tree_before || tree_after) && !self.matcher.visit(&path).is_nothing();
let file_matches = (!tree_before || !tree_after) && self.matcher.matches(&path);
let before = if (tree_before && tree_matches) || (!tree_before && file_matches) {
diff.before
} else {
Merge::absent()
};
let after = if (tree_after && tree_matches) || (!tree_after && file_matches) {
diff.after
} else {
Merge::absent()
};
if before.is_absent() && after.is_absent() {
continue;
}
if tree_matches {
let before_tree_future =
Self::trees(self.store.clone(), path.clone(), before.cloned());
let after_tree_future =
Self::trees(self.store.clone(), path.clone(), after.cloned());
let both_trees_future = try_join(before_tree_future, after_tree_future);
self.pending_trees
.insert(path.clone(), Box::pin(both_trees_future));
}
if file_matches || self.matcher.matches(&path) {
self.items
.insert(path, Ok(Diff::new(before.cloned(), after.cloned())));
}
}
}
fn poll_tree_futures(&mut self, cx: &mut Context<'_>) {
loop {
let mut tree_diffs = vec![];
let mut some_pending = false;
let mut all_pending = true;
for (dir, future) in self
.pending_trees
.iter_mut()
.take(self.max_concurrent_reads)
{
if let Poll::Ready(tree_diff) = future.as_mut().poll(cx) {
all_pending = false;
tree_diffs.push((dir.clone(), tree_diff));
} else {
some_pending = true;
}
}
for (dir, tree_diff) in tree_diffs {
drop(self.pending_trees.remove_entry(&dir).unwrap());
match tree_diff {
Ok((trees1, trees2)) => {
self.add_dir_diff_items(&dir, &trees1, &trees2);
}
Err(err) => {
self.items.insert(dir, Err(err));
}
}
}
if all_pending || (some_pending && self.items.len() >= self.max_queued_items) {
return;
}
}
}
}
impl Stream for TreeDiffStreamImpl<'_> {
type Item = TreeDiffEntry;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.poll_tree_futures(cx);
if let Some((path, _)) = self.items.first_key_value() {
if let Some((dir, _)) = self.pending_trees.first_key_value()
&& dir < path
{
return Poll::Pending;
}
let (path, values) = self.items.pop_first().unwrap();
Poll::Ready(Some(TreeDiffEntry { path, values }))
} else if self.pending_trees.is_empty() {
Poll::Ready(None)
} else {
Poll::Pending
}
}
}
fn stream_without_trees(stream: TreeDiffStream) -> TreeDiffStream {
stream
.filter_map(|mut entry| async move {
let skip_tree = |merge: MergedTreeValue| {
if merge.is_tree() {
Merge::absent()
} else {
merge
}
};
entry.values = entry.values.map(|diff| diff.map(skip_tree));
let any_present = entry.values.as_ref().map_or(true, |diff| {
diff.before.is_present() || diff.after.is_present()
});
any_present.then_some(entry)
})
.boxed()
}
struct DiffStreamForFileSystem<'a> {
inner: TreeDiffStream<'a>,
next_item: Option<TreeDiffEntry>,
held_file: Option<TreeDiffEntry>,
}
impl<'a> DiffStreamForFileSystem<'a> {
fn new(inner: TreeDiffStream<'a>) -> Self {
Self {
inner,
next_item: None,
held_file: None,
}
}
}
impl Stream for DiffStreamForFileSystem<'_> {
type Item = TreeDiffEntry;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
while let Some(next) = match self.next_item.take() {
Some(next) => Some(next),
None => ready!(self.inner.as_mut().poll_next(cx)),
} {
if let Ok(diff) = &next.values
&& !diff.before.is_file_like()
&& !diff.after.is_file_like()
{
continue;
}
if let Some(held_entry) = self
.held_file
.take_if(|held_entry| !next.path.starts_with(&held_entry.path))
{
self.next_item = Some(next);
return Poll::Ready(Some(held_entry));
}
match next.values {
Ok(diff) if diff.before.is_tree() => {
assert!(diff.after.is_present());
assert!(self.held_file.is_none());
self.held_file = Some(TreeDiffEntry {
path: next.path,
values: Ok(Diff::new(Merge::absent(), diff.after)),
});
}
Ok(diff) if diff.after.is_tree() => {
assert!(diff.before.is_present());
return Poll::Ready(Some(TreeDiffEntry {
path: next.path,
values: Ok(Diff::new(diff.before, Merge::absent())),
}));
}
_ => {
return Poll::Ready(Some(next));
}
}
}
Poll::Ready(self.held_file.take())
}
}