Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: GitoxideLabs/gitoxide
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 5b8140f48cbdc5abc6f280d07547ef466c39b878
Choose a base ref
..
head repository: GitoxideLabs/gitoxide
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 21baf6fc6c60d5ade046cfca5efaacf000a39d60
Choose a head ref
53 changes: 42 additions & 11 deletions gitoxide-core/src/repository/status.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::bail;
use gix::bstr::{BStr, BString, ByteSlice};
use gix::status::index_worktree::Item;
use gix::status::{self, index_worktree};
use gix_status::index_as_worktree::{Change, Conflict, EntryStatus};
use std::path::Path;

@@ -109,21 +109,53 @@ pub fn show(
}
None => gix::status::Submodule::AsConfigured { check_dirty: false },
})
.into_index_worktree_iter(pathspecs)?;
.into_iter(pathspecs)?;

for item in iter.by_ref() {
let item = item?;
match item {
Item::Modification {
status::Item::TreeIndex(change) => {
let (location, _, _, _) = change.fields();
let status = match change {
gix::diff::index::Change::Addition { .. } => "A",
gix::diff::index::Change::Deletion { .. } => "D",
gix::diff::index::Change::Modification { .. } => "M",
gix::diff::index::Change::Rewrite {
ref source_location, ..
} => {
let source_location = gix::path::from_bstr(source_location.as_ref());
let source_location = gix::path::relativize_with_prefix(&source_location, prefix);
writeln!(
out,
"{status: >2} {source_rela_path} → {dest_rela_path}",
status = "R",
source_rela_path = source_location.display(),
dest_rela_path =
gix::path::relativize_with_prefix(&gix::path::from_bstr(location), prefix).display(),
)?;
continue;
}
gix::diff::index::Change::Unmerged { .. } => {
// TODO: should this be displayed? How to prevent double-display?
continue;
}
};
writeln!(
out,
"{status: >2} {rela_path}",
rela_path = gix::path::relativize_with_prefix(&gix::path::from_bstr(location), prefix).display(),
)?;
}
status::Item::IndexWorktree(index_worktree::Item::Modification {
entry: _,
entry_index: _,
rela_path,
status,
} => print_index_entry_status(&mut out, prefix, rela_path.as_ref(), status)?,
Item::DirectoryContents {
}) => print_index_entry_status(&mut out, prefix, rela_path.as_ref(), status)?,
status::Item::IndexWorktree(index_worktree::Item::DirectoryContents {
entry,
collapsed_directory_status,
} => {
}) => {
if collapsed_directory_status.is_none() {
writeln!(
out,
@@ -139,12 +171,12 @@ pub fn show(
)?;
}
}
Item::Rewrite {
status::Item::IndexWorktree(index_worktree::Item::Rewrite {
source,
dirwalk_entry,
copy: _, // TODO: how to visualize copies?
..
} => {
}) => {
// TODO: handle multi-status characters, there can also be modifications at the same time as determined by their ID and potentially diffstats.
writeln!(
out,
@@ -175,9 +207,8 @@ pub fn show(
writeln!(err, "{outcome:#?}", outcome = out.index_worktree).ok();
}

writeln!(err, "\nhead -> index isn't implemented yet")?;
progress.init(Some(out.index.entries().len()), gix::progress::count("files"));
progress.set(out.index.entries().len());
progress.init(Some(out.worktree_index.entries().len()), gix::progress::count("files"));
progress.set(out.worktree_index.entries().len());
progress.show_throughput(start);
Ok(())
}
7 changes: 6 additions & 1 deletion gix/src/dirwalk/iter.rs
Original file line number Diff line number Diff line change
@@ -160,7 +160,12 @@ impl Iterator for Iter {
#[cfg(feature = "parallel")]
impl Drop for Iter {
fn drop(&mut self) {
crate::util::parallel_iter_drop(self.rx_and_join.take(), &self.should_interrupt);
crate::util::parallel_iter_drop(
self.rx_and_join
.take()
.map(|(rx, handle)| (rx, handle, None::<std::thread::JoinHandle<()>>)),
&self.should_interrupt,
);
}
}

45 changes: 34 additions & 11 deletions gix/src/status/index_worktree.rs
Original file line number Diff line number Diff line change
@@ -110,16 +110,8 @@ impl Repository {
crate::worktree::stack::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped,
None,
)?;
let pathspec = crate::Pathspec::new(
self,
options
.dirwalk_options
.as_ref()
.map_or(false, |opts| opts.empty_patterns_match_prefix),
patterns,
true, /* inherit ignore case */
|| Ok(attrs_and_excludes.clone()),
)?;
let pathspec =
self.index_worktree_status_pathspec::<Error>(patterns, index, options.dirwalk_options.as_ref())?;

let cwd = self.current_dir();
let git_dir_realpath = crate::path::realpath_opts(self.git_dir(), cwd, crate::path::realpath::MAX_SYMLINKS)?;
@@ -167,6 +159,31 @@ impl Repository {
)?;
Ok(out)
}

pub(super) fn index_worktree_status_pathspec<E>(
&self,
patterns: impl IntoIterator<Item = impl AsRef<BStr>>,
index: &gix_index::State,
options: Option<&crate::dirwalk::Options>,
) -> Result<crate::Pathspec<'_>, E>
where
E: From<crate::repository::attributes::Error> + From<crate::pathspec::init::Error>,
{
let empty_patterns_match_prefix = options.map_or(false, |opts| opts.empty_patterns_match_prefix);
let attrs_and_excludes = self.attributes(
index,
crate::worktree::stack::state::attributes::Source::WorktreeThenIdMapping,
crate::worktree::stack::state::ignore::Source::WorktreeThenIdMappingIfNotSkipped,
None,
)?;
Ok(crate::Pathspec::new(
self,
empty_patterns_match_prefix,
patterns,
true, /* inherit ignore case */
move || Ok(attrs_and_excludes.inner),
)?)
}
}

/// An implementation of a trait to use with [`Repository::index_worktree_status()`] to compute the submodule status
@@ -550,7 +567,13 @@ pub mod iter {
self.inner.next().map(|res| {
res.map(|item| match item {
crate::status::Item::IndexWorktree(item) => item,
crate::status::Item::TreeIndex => unreachable!("BUG: we deactivated this kind of traversal"),
crate::status::Item::TreeIndex(_) => unreachable!("BUG: we deactivated this kind of traversal"),
})
.map_err(|err| match err {
crate::status::iter::Error::IndexWorktree(err) => err,
crate::status::iter::Error::TreeIndex(_) => {
unreachable!("BUG: we deactivated this kind of traversal")
}
})
})
}
134 changes: 110 additions & 24 deletions gix/src/status/iter/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::bstr::BString;
use crate::config::cache::util::ApplyLeniencyDefault;
use crate::status::index_worktree::BuiltinSubmoduleStatus;
use crate::status::{index_worktree, Platform};
use crate::status::{index_worktree, tree_index, Platform};
use crate::worktree::IndexPersistedOrInMemory;
use gix_status::index_as_worktree::{Change, EntryStatus};
use std::sync::atomic::Ordering;

pub(super) mod types;
use types::{ApplyChange, Item, Iter, Outcome};
@@ -31,7 +32,7 @@ where
let obtain_tree_id = || -> Result<Option<gix_hash::ObjectId>, crate::status::into_iter::Error> {
Ok(match self.head_tree {
Some(None) => Some(self.repo.head_tree_id()?.into()),
Some(Some(tree_id)) => Some(tree_id.into()),
Some(Some(tree_id)) => Some(tree_id),
None => None,
})
};
@@ -50,9 +51,55 @@ where
#[cfg(feature = "parallel")]
{
let (tx, rx) = std::sync::mpsc::channel();
let mut collect = Collect { tx };
let patterns: Vec<_> = patterns.into_iter().collect();
let join = std::thread::Builder::new()
let join_tree_index = if let Some(tree_id) = obtain_tree_id()? {
std::thread::Builder::new()
.name("gix::status::tree_index::producer".into())
.spawn({
let repo = self.repo.clone().into_sync();
let should_interrupt = should_interrupt.clone();
let tx = tx.clone();
let tree_index_renames = self.tree_index_renames;
let index = index.clone();
let crate::Pathspec { repo: _, stack, search } = self
.repo
.index_worktree_status_pathspec::<crate::status::into_iter::Error>(
&patterns,
&index,
self.index_worktree_options.dirwalk_options.as_ref(),
)?;
move || -> Result<_, _> {
let repo = repo.to_thread_local();
let mut pathspec = crate::Pathspec {
repo: &repo,
stack,
search,
};
repo.tree_index_status(
&tree_id,
&index,
Some(&mut pathspec),
tree_index_renames,
|change, _, _| {
let action = if tx.send(change.into_owned().into()).is_err()
|| should_interrupt.load(Ordering::Acquire)
{
gix_diff::index::Action::Cancel
} else {
gix_diff::index::Action::Continue
};
Ok::<_, std::convert::Infallible>(action)
},
)
}
})
.map_err(crate::status::into_iter::Error::SpawnThread)?
.into()
} else {
None
};
let mut collect = Collect { tx };
let join_index_worktree = std::thread::Builder::new()
.name("gix::status::index_worktree::producer".into())
.spawn({
let repo = self.repo.clone().into_sync();
@@ -73,7 +120,8 @@ where
)?;
Ok(Outcome {
index_worktree: out,
index,
tree_index: None,
worktree_index: index,
changes: None,
skip_hash,
})
@@ -82,7 +130,7 @@ where
.map_err(crate::status::into_iter::Error::SpawnThread)?;

Ok(Iter {
rx_and_join: Some((rx, join)),
rx_and_join: Some((rx, join_index_worktree, join_tree_index)),
should_interrupt,
index_changes: Vec::new(),
out: None,
@@ -92,16 +140,36 @@ where
{
let mut collect = Collect { items: Vec::new() };

let repo = self.repo.clone().into_sync();
let repo = self.repo;
let options = self.index_worktree_options;
let mut progress = self.progress;
let repo = repo.to_thread_local();
let items = match obtain_tree_id()? {
let patterns: Vec<BString> = patterns.into_iter().collect();
let (mut items, tree_index) = match obtain_tree_id()? {
Some(tree_id) => {
// self.repo.tree_index_status(&tree_id);
todo!()
let mut pathspec = repo.index_worktree_status_pathspec::<crate::status::into_iter::Error>(
&patterns,
&index,
self.index_worktree_options.dirwalk_options.as_ref(),
)?;
let mut items = Vec::new();
let tree_index = self.repo.tree_index_status(
&tree_id,
&index,
Some(&mut pathspec),
self.tree_index_renames,
|change, _, _| {
items.push(change.into_owned().into());
let action = if should_interrupt.load(Ordering::Acquire) {
gix_diff::index::Action::Cancel
} else {
gix_diff::index::Action::Continue
};
Ok::<_, std::convert::Infallible>(action)
},
)?;
(items, Some(tree_index))
}
None => Vec::new().into_iter(),
None => (Vec::new(), None),
};
let out = repo.index_worktree_status(
&index,
@@ -114,21 +182,23 @@ where
options,
)?;
let mut iter = Iter {
items,
items: Vec::new().into_iter(),
index_changes: Vec::new(),
out: None,
};
let mut out = Outcome {
index_worktree: out,
index,
worktree_index: index,
tree_index,
changes: None,
skip_hash,
};
let items = collect
.items
.into_iter()
.filter_map(|item| iter.maybe_keep_index_change(item))
.collect::<Vec<_>>();
items.extend(
collect
.items
.into_iter()
.filter_map(|item| iter.maybe_keep_index_change(item)),
);
out.changes = (!iter.index_changes.is_empty()).then(|| std::mem::take(&mut iter.index_changes));
iter.items = items.into_iter();
iter.out = Some(out);
@@ -138,15 +208,22 @@ where
}

/// The error returned for each item returned by [`Iter`].
pub type Error = index_worktree::Error;
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
IndexWorktree(#[from] index_worktree::Error),
#[error(transparent)]
TreeIndex(#[from] tree_index::Error),
}

impl Iterator for Iter {
type Item = Result<Item, Error>;

fn next(&mut self) -> Option<Self::Item> {
#[cfg(feature = "parallel")]
loop {
let (rx, _join) = self.rx_and_join.as_ref()?;
let (rx, _join_worktree, _join_tree) = self.rx_and_join.as_ref()?;
match rx.recv().ok() {
Some(item) => {
if let Some(item) = self.maybe_keep_index_change(item) {
@@ -155,14 +232,23 @@ impl Iterator for Iter {
continue;
}
None => {
let (_rx, handle) = self.rx_and_join.take()?;
break match handle.join().expect("no panic") {
let (_rx, worktree_handle, tree_handle) = self.rx_and_join.take()?;
let tree_index = if let Some(handle) = tree_handle {
match handle.join().expect("no panic") {
Ok(out) => Some(out),
Err(err) => break Some(Err(err.into())),
}
} else {
None
};
break match worktree_handle.join().expect("no panic") {
Ok(mut out) => {
out.changes = Some(std::mem::take(&mut self.index_changes));
out.tree_index = tree_index;
self.out = Some(out);
None
}
Err(err) => Some(Err(err)),
Err(err) => Some(Err(err.into())),
};
}
}
Loading