Skip to content

Commit 81ee2bc

Browse files
committed
feat!: improve index_as_worktree() function signature.
This is accomplished by providing a context which now contains only statically known types, among them also a pathspec search. This allows the attribute stack to be passed and can thus cheaply be cloned, instead of being created internally.
1 parent ed705dc commit 81ee2bc

File tree

8 files changed

+84
-75
lines changed

8 files changed

+84
-75
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-status/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ gix-path = { version = "^0.10.5", path = "../gix-path" }
2222
gix-features = { version = "^0.38.0", path = "../gix-features" }
2323
gix-filter = { version = "^0.9.0", path = "../gix-filter" }
2424
gix-worktree = { version = "^0.31.0", path = "../gix-worktree", default-features = false, features = ["attributes"] }
25+
gix-pathspec = { version = "^0.6.0", path = "../gix-pathspec" }
2526

2627
thiserror = "1.0.26"
2728
filetime = "0.2.15"

gix-status/src/index_as_worktree/function.rs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{
22
io,
33
path::Path,
44
slice::Chunks,
5-
sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
5+
sync::atomic::{AtomicU64, AtomicUsize, Ordering},
66
};
77

88
use bstr::BStr;
@@ -11,30 +11,30 @@ use gix_features::parallel::{in_parallel_if, Reduce};
1111
use gix_filter::pipeline::convert::ToGitOutcome;
1212
use gix_object::FindExt;
1313

14+
use crate::index_as_worktree::Context;
1415
use crate::{
1516
index_as_worktree::{
1617
traits,
1718
traits::{read_data::Stream, CompareBlobs, SubmoduleStatus},
1819
types::{Error, Options},
1920
Change, Conflict, EntryStatus, Outcome, VisitEntry,
2021
},
21-
Pathspec, SymlinkCheck,
22+
SymlinkCheck,
2223
};
2324

2425
/// Calculates the changes that need to be applied to an `index` to match the state of the `worktree` and makes them
2526
/// observable in `collector`, along with information produced by `compare` which gets to see blobs that may have changes, and
2627
/// `submodule` which can take a look at submodules in detail to produce status information (BASE version if its conflicting).
2728
/// `options` are used to configure the operation.
2829
///
30+
/// Note `worktree` must be the root path of the worktree, not a path inside of the worktree.
31+
///
2932
/// Note that `index` may require changes to be up-to-date with the working tree and avoid expensive computations by updating
3033
/// respective entries with stat information from the worktree, and its timestamp is adjusted to the current time for which it
3134
/// will be considered fresh. All changes that would be applied to the index are delegated to the caller, which receives these
3235
/// as [`EntryStatus`].
3336
/// The `pathspec` is used to determine which index entries to check for status in the first place.
3437
///
35-
/// `should_interrupt` can be used to stop all processing.
36-
/// `filter` is used to convert worktree files back to their internal git representation. For this to be correct,
37-
/// [`Options::attributes`] must be configured as well.
3838
/// `objects` is used to access the version of an object in the object database for direct comparison.
3939
///
4040
/// **It's important to note that the `index` should have its [timestamp updated](gix_index::State::set_timestamp()) with a timestamp
@@ -47,7 +47,7 @@ use crate::{
4747
/// stats like `git status` would if it had to determine the hash.
4848
/// If that happened, the index should be written back after updating the entries with these updated stats, see [Outcome::skipped].
4949
///
50-
/// Thus some care has to be taken to do the right thing when letting the index match the worktree by evaluating the changes observed
50+
/// Thus, some care has to be taken to do the right thing when letting the index match the worktree by evaluating the changes observed
5151
/// by the `collector`.
5252
#[allow(clippy::too_many_arguments)]
5353
pub fn index_as_worktree<'index, T, U, Find, E>(
@@ -58,10 +58,13 @@ pub fn index_as_worktree<'index, T, U, Find, E>(
5858
submodule: impl SubmoduleStatus<Output = U, Error = E> + Send + Clone,
5959
objects: Find,
6060
progress: &mut dyn gix_features::progress::Progress,
61-
pathspec: impl Pathspec + Send + Clone,
62-
filter: gix_filter::Pipeline,
63-
should_interrupt: &AtomicBool,
64-
mut options: Options,
61+
Context {
62+
pathspec,
63+
stack,
64+
filter,
65+
should_interrupt,
66+
}: Context<'_>,
67+
options: Options,
6568
) -> Result<Outcome, Error>
6669
where
6770
T: Send,
@@ -84,20 +87,13 @@ where
8487
.prefixed_entries_range(pathspec.common_prefix())
8588
.unwrap_or(0..index.entries().len());
8689

87-
let stack = gix_worktree::Stack::from_state_and_ignore_case(
88-
worktree,
89-
options.fs.ignore_case,
90-
gix_worktree::stack::State::AttributesStack(std::mem::take(&mut options.attributes)),
91-
index,
92-
index.path_backing(),
93-
);
9490
let (entries, path_backing) = (index.entries(), index.path_backing());
9591
let mut num_entries = entries.len();
9692
let entry_index_offset = range.start;
9793
let entries = &entries[range];
9894

9995
let _span = gix_features::trace::detail!("gix_status::index_as_worktree",
100-
num_entries = entries.len(),
96+
num_entries = entries.len(),
10197
chunk_size = chunk_size,
10298
thread_limit = ?thread_limit);
10399

@@ -253,7 +249,7 @@ impl<'index> State<'_, 'index> {
253249
entries: &'index [gix_index::Entry],
254250
entry: &'index gix_index::Entry,
255251
entry_index: usize,
256-
pathspec: &mut impl Pathspec,
252+
pathspec: &mut gix_pathspec::Search,
257253
diff: &mut impl CompareBlobs<Output = T>,
258254
submodule: &mut impl SubmoduleStatus<Output = U, Error = E>,
259255
objects: &Find,
@@ -273,7 +269,20 @@ impl<'index> State<'_, 'index> {
273269
return None;
274270
}
275271
let path = entry.path_in(self.path_backing);
276-
if !pathspec.is_included(path, Some(false)) {
272+
let is_excluded = pathspec
273+
.pattern_matching_relative_path(
274+
path,
275+
Some(entry.mode.is_submodule()),
276+
&mut |relative_path, case, is_dir, out| {
277+
self.attr_stack
278+
.set_case(case)
279+
.at_entry(relative_path, Some(is_dir), objects)
280+
.map_or(false, |platform| platform.matching_attributes(out))
281+
},
282+
)
283+
.map_or(true, |m| m.is_excluded());
284+
285+
if is_excluded {
277286
self.skipped_by_pathspec.fetch_add(1, Ordering::Relaxed);
278287
return None;
279288
}

gix-status/src/index_as_worktree/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Changes between an index and a worktree.
22
///
33
mod types;
4-
pub use types::{Change, Conflict, EntryStatus, Error, Options, Outcome, VisitEntry};
4+
pub use types::{Change, Conflict, Context, EntryStatus, Error, Options, Outcome, VisitEntry};
55

66
mod recorder;
77
pub use recorder::{Record, Recorder};

gix-status/src/index_as_worktree/types.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use bstr::{BStr, BString};
2+
use std::sync::atomic::AtomicBool;
23

34
/// The error returned by [index_as_worktree()`](crate::index_as_worktree()).
45
#[derive(Debug, thiserror::Error)]
@@ -30,11 +31,25 @@ pub struct Options {
3031
pub thread_limit: Option<usize>,
3132
/// Options that control how stat comparisons are made when checking if a file is fresh.
3233
pub stat: gix_index::entry::stat::Options,
33-
/// Pre-configured state to allow processing attributes.
34+
}
35+
36+
/// The context for [index_as_worktree()`](crate::index_as_worktree()).
37+
#[derive(Clone)]
38+
pub struct Context<'a> {
39+
/// The pathspec to limit the amount of paths that are checked. Can be empty to allow all paths.
40+
pub pathspec: gix_pathspec::Search,
41+
/// A stack pre-configured to allow accessing attributes for each entry, as required for `filter`
42+
/// and possibly pathspecs.
43+
pub stack: gix_worktree::Stack,
44+
/// A filter to be able to perform conversions from and to the worktree format.
45+
///
46+
/// It is needed to potentially refresh the index with data read from the worktree, which needs to be converted back
47+
/// to the form stored in Git.
3448
///
35-
/// These are needed to potentially refresh the index with data read from the worktree, which needs to be converted back
36-
/// to the form stored in git.
37-
pub attributes: gix_worktree::stack::state::Attributes,
49+
/// Note that for this to be correct, the attribute `stack` must be configured correctly as well.
50+
pub filter: gix_filter::Pipeline,
51+
/// A flag to query to learn if cancellation is requested.
52+
pub should_interrupt: &'a AtomicBool,
3853
}
3954

4055
/// Provide additional information collected during the runtime of [`index_as_worktree()`](crate::index_as_worktree()).

gix-status/src/lib.rs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,9 @@
88
//! While also being able to check check if the working tree is dirty, quickly.
99
#![deny(missing_docs, rust_2018_idioms, unsafe_code)]
1010

11-
use bstr::BStr;
12-
1311
pub mod index_as_worktree;
1412
pub use index_as_worktree::function::index_as_worktree;
1513

16-
/// A trait to facilitate working working with pathspecs.
17-
pub trait Pathspec {
18-
/// Return the portion of the prefix among all of the pathspecs involved in this search, or an empty string if
19-
/// there is none. It doesn't have to end at a directory boundary though, nor does it denote a directory.
20-
///
21-
/// Note that the common_prefix is always matched case-sensitively, and it is useful to skip large portions of input.
22-
/// Further, excluded pathspecs don't participate which makes this common prefix inclusive. To work correctly though,
23-
/// one will have to additionally match paths that have the common prefix with that pathspec itself to assure it is
24-
/// not excluded.
25-
fn common_prefix(&self) -> &BStr;
26-
27-
/// Return `true` if `relative_path` is included in this pathspec.
28-
/// `is_dir` is `true` if `relative_path` is a directory.
29-
fn is_included(&mut self, relative_path: &BStr, is_dir: Option<bool>) -> bool;
30-
}
31-
3214
/// A stack that validates we are not going through a symlink in a way that is read-only.
3315
///
3416
/// It can efficiently validate paths when these are queried in sort-order, which leads to each component

gix-status/tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ gix-hash = { path = "../../gix-hash" }
2525
gix-object = { path = "../../gix-object" }
2626
gix-features = { path = "../../gix-features" }
2727
gix-pathspec = { path = "../../gix-pathspec" }
28+
gix-worktree = { path = "../../gix-worktree" }
2829
filetime = "0.2.15"
2930
bstr = { version = "1.3.0", default-features = false }
3031

gix-status/tests/status/index_as_worktree.rs

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use bstr::BStr;
77
use filetime::{set_file_mtime, FileTime};
88
use gix_index as index;
99
use gix_index::Entry;
10+
use gix_status::index_as_worktree::Context;
1011
use gix_status::{
1112
index_as_worktree,
1213
index_as_worktree::{
@@ -93,6 +94,13 @@ fn fixture_filtered_detailed(
9394
let mut recorder = Recorder::default();
9495
let search = gix_pathspec::Search::from_specs(to_pathspecs(pathspecs), None, std::path::Path::new(""))
9596
.expect("valid specs can be normalized");
97+
let stack = gix_worktree::Stack::from_state_and_ignore_case(
98+
worktree.clone(),
99+
false,
100+
gix_worktree::stack::State::AttributesStack(Default::default()),
101+
&index,
102+
index.path_backing(),
103+
);
96104
let outcome = index_as_worktree(
97105
&index,
98106
&worktree,
@@ -101,9 +109,12 @@ fn fixture_filtered_detailed(
101109
SubmoduleStatusMock { dirty: submodule_dirty },
102110
gix_object::find::Never,
103111
&mut gix_features::progress::Discard,
104-
Pathspec(search),
105-
Default::default(),
106-
&AtomicBool::default(),
112+
Context {
113+
pathspec: search,
114+
stack,
115+
filter: Default::default(),
116+
should_interrupt: &AtomicBool::default(),
117+
},
107118
Options {
108119
fs: gix_fs::Capabilities::probe(&git_dir),
109120
stat: TEST_OPTIONS,
@@ -606,6 +617,19 @@ fn racy_git() {
606617

607618
let count = Arc::new(AtomicUsize::new(0));
608619
let counter = CountCalls(count.clone(), FastEq);
620+
let stack = gix_worktree::Stack::from_state_and_ignore_case(
621+
worktree,
622+
false,
623+
gix_worktree::stack::State::AttributesStack(Default::default()),
624+
&index,
625+
index.path_backing(),
626+
);
627+
let ctx = Context {
628+
pathspec: default_pathspec(),
629+
stack,
630+
filter: Default::default(),
631+
should_interrupt: &AtomicBool::default(),
632+
};
609633
let out = index_as_worktree(
610634
&index,
611635
worktree,
@@ -614,9 +638,7 @@ fn racy_git() {
614638
SubmoduleStatusMock { dirty: false },
615639
gix_object::find::Never,
616640
&mut gix_features::progress::Discard,
617-
Pathspec::default(),
618-
Default::default(),
619-
&AtomicBool::default(),
641+
ctx.clone(),
620642
Options {
621643
fs,
622644
stat: TEST_OPTIONS,
@@ -653,9 +675,7 @@ fn racy_git() {
653675
SubmoduleStatusMock { dirty: false },
654676
gix_object::find::Never,
655677
&mut gix_features::progress::Discard,
656-
Pathspec::default(),
657-
Default::default(),
658-
&AtomicBool::default(),
678+
ctx,
659679
Options {
660680
fs,
661681
stat: TEST_OPTIONS,
@@ -696,27 +716,6 @@ fn racy_git() {
696716
);
697717
}
698718

699-
#[derive(Clone)]
700-
struct Pathspec(gix_pathspec::Search);
701-
702-
impl Default for Pathspec {
703-
fn default() -> Self {
704-
let search = gix_pathspec::Search::from_specs(to_pathspecs(&[]), None, std::path::Path::new(""))
705-
.expect("empty is always valid");
706-
Self(search)
707-
}
708-
}
709-
710-
impl gix_status::Pathspec for Pathspec {
711-
fn common_prefix(&self) -> &BStr {
712-
self.0.common_prefix()
713-
}
714-
715-
fn is_included(&mut self, relative_path: &BStr, is_dir: Option<bool>) -> bool {
716-
self.0
717-
.pattern_matching_relative_path(relative_path, is_dir, &mut |_, _, _, _| {
718-
unreachable!("we don't use attributes in our pathspecs")
719-
})
720-
.map_or(false, |m| !m.is_excluded())
721-
}
719+
fn default_pathspec() -> gix_pathspec::Search {
720+
gix_pathspec::Search::from_specs(to_pathspecs(&[]), None, std::path::Path::new("")).expect("empty is always valid")
722721
}

0 commit comments

Comments
 (0)