Skip to content

Commit 8d84818

Browse files
authored
Merge pull request #1749 from GitoxideLabs/status
various improvements
2 parents 5f21c2e + b67c39d commit 8d84818

File tree

6 files changed

+175
-35
lines changed

6 files changed

+175
-35
lines changed

gix-status/tests/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ gix-dir = { path = "../../gix-dir" }
3030
gix-odb = { path = "../../gix-odb" }
3131
gix-hash = { path = "../../gix-hash" }
3232
gix-object = { path = "../../gix-object" }
33-
gix-features = { path = "../../gix-features" }
33+
gix-features = { path = "../../gix-features", features = ["parallel"] }
3434
gix-pathspec = { path = "../../gix-pathspec" }
3535
gix-worktree = { path = "../../gix-worktree" }
3636
filetime = "0.2.15"

gix-status/tests/fixtures/generated-archives/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ status_unchanged.tar
22
status_changed.tar
33
symlink_stack.tar
44
status_nonfile.tar
5+
status_unchanged_filter.tar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
6+
touch empty
7+
echo -n "content" >executable
8+
chmod +x executable
9+
10+
mkdir dir
11+
echo "other content" >dir/content
12+
seq 5 >dir/content2
13+
mkdir dir/sub-dir
14+
(cd dir/sub-dir && ln -sf ../content symlink)
15+
16+
git add -A
17+
git update-index --chmod=+x executable # For Windows.
18+
git commit -m "Commit"
19+
20+
git ls-files | xargs rm
21+
22+
git config core.autocrlf true
23+
git checkout -f HEAD

gix-status/tests/status/index_as_worktree.rs

+135-26
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::sync::{
55

66
use bstr::BStr;
77
use filetime::{set_file_mtime, FileTime};
8+
use gix_filter::eol::AutoCrlf;
89
use gix_index as index;
910
use gix_index::Entry;
1011
use gix_status::index_as_worktree::Context;
@@ -38,40 +39,97 @@ fn fixture(name: &str, expected_status: &[Expectation<'_>]) -> Outcome {
3839
}
3940

4041
fn nonfile_fixture(name: &str, expected_status: &[Expectation<'_>]) -> Outcome {
41-
fixture_filtered_detailed("status_nonfile", name, &[], expected_status, |_| {}, false)
42+
fixture_filtered_detailed(
43+
"status_nonfile",
44+
name,
45+
&[],
46+
expected_status,
47+
|_| {},
48+
false,
49+
Default::default(),
50+
false,
51+
)
4252
}
4353

4454
fn fixture_with_index(
4555
name: &str,
4656
prepare_index: impl FnMut(&mut gix_index::State),
4757
expected_status: &[Expectation<'_>],
4858
) -> Outcome {
49-
fixture_filtered_detailed(name, "", &[], expected_status, prepare_index, false)
59+
fixture_filtered_detailed(
60+
name,
61+
"",
62+
&[],
63+
expected_status,
64+
prepare_index,
65+
false,
66+
Default::default(),
67+
false,
68+
)
5069
}
5170

5271
fn submodule_fixture(name: &str, expected_status: &[Expectation<'_>]) -> Outcome {
53-
fixture_filtered_detailed("status_submodule", name, &[], expected_status, |_| {}, false)
72+
fixture_filtered_detailed(
73+
"status_submodule",
74+
name,
75+
&[],
76+
expected_status,
77+
|_| {},
78+
false,
79+
Default::default(),
80+
false,
81+
)
5482
}
5583

5684
fn conflict_fixture(name: &str, expected_status: &[Expectation<'_>]) -> Outcome {
57-
fixture_filtered_detailed("conflicts", name, &[], expected_status, |_| {}, false)
85+
fixture_filtered_detailed(
86+
"conflicts",
87+
name,
88+
&[],
89+
expected_status,
90+
|_| {},
91+
false,
92+
Default::default(),
93+
false,
94+
)
5895
}
5996

6097
fn submodule_fixture_status(name: &str, expected_status: &[Expectation<'_>], submodule_dirty: bool) -> Outcome {
61-
fixture_filtered_detailed("status_submodule", name, &[], expected_status, |_| {}, submodule_dirty)
98+
fixture_filtered_detailed(
99+
"status_submodule",
100+
name,
101+
&[],
102+
expected_status,
103+
|_| {},
104+
submodule_dirty,
105+
Default::default(),
106+
false,
107+
)
62108
}
63109

64110
fn fixture_filtered(name: &str, pathspecs: &[&str], expected_status: &[Expectation<'_>]) -> Outcome {
65-
fixture_filtered_detailed(name, "", pathspecs, expected_status, |_| {}, false)
111+
fixture_filtered_detailed(
112+
name,
113+
"",
114+
pathspecs,
115+
expected_status,
116+
|_| {},
117+
false,
118+
Default::default(),
119+
false,
120+
)
66121
}
67122

123+
#[allow(clippy::too_many_arguments)]
68124
fn fixture_filtered_detailed(
69125
name: &str,
70126
subdir: &str,
71127
pathspecs: &[&str],
72128
expected_status: &[Expectation<'_>],
73129
mut prepare_index: impl FnMut(&mut gix_index::State),
74130
submodule_dirty: bool,
131+
auto_crlf: gix_filter::eol::AutoCrlf,
132+
use_odb: bool,
75133
) -> Outcome {
76134
// This can easily happen in some fixtures, which can cause flakiness. It's time-dependent after all.
77135
fn ignore_racyclean(mut out: Outcome) -> Outcome {
@@ -105,26 +163,53 @@ fn fixture_filtered_detailed(
105163
&index,
106164
index.path_backing(),
107165
);
108-
let outcome = index_as_worktree(
109-
&index,
110-
&worktree,
111-
&mut recorder,
112-
FastEq,
113-
SubmoduleStatusMock { dirty: submodule_dirty },
114-
gix_object::find::Never,
115-
&mut gix_features::progress::Discard,
116-
Context {
117-
pathspec: search,
118-
stack,
119-
filter: Default::default(),
120-
should_interrupt: &AtomicBool::default(),
121-
},
122-
Options {
123-
fs: gix_fs::Capabilities::probe(&git_dir),
124-
stat: TEST_OPTIONS,
125-
..Options::default()
126-
},
127-
)
166+
let ctx = Context {
167+
pathspec: search,
168+
stack,
169+
filter: gix_filter::Pipeline::new(
170+
Default::default(),
171+
gix_filter::pipeline::Options {
172+
eol_config: gix_filter::eol::Configuration {
173+
auto_crlf,
174+
..Default::default()
175+
},
176+
..Default::default()
177+
},
178+
),
179+
should_interrupt: &AtomicBool::default(),
180+
};
181+
let options = Options {
182+
fs: gix_fs::Capabilities::probe(&git_dir),
183+
stat: TEST_OPTIONS,
184+
..Options::default()
185+
};
186+
let outcome = if use_odb {
187+
let odb = gix_odb::at(git_dir.join("objects")).unwrap().into_arc().unwrap();
188+
index_as_worktree(
189+
&index,
190+
&worktree,
191+
&mut recorder,
192+
FastEq,
193+
SubmoduleStatusMock { dirty: submodule_dirty },
194+
odb,
195+
&mut gix_features::progress::Discard,
196+
ctx,
197+
options,
198+
)
199+
} else {
200+
let odb = gix_object::find::Never;
201+
index_as_worktree(
202+
&index,
203+
&worktree,
204+
&mut recorder,
205+
FastEq,
206+
SubmoduleStatusMock { dirty: submodule_dirty },
207+
&odb,
208+
&mut gix_features::progress::Discard,
209+
ctx,
210+
options,
211+
)
212+
}
128213
.unwrap();
129214
recorder.records.sort_unstable_by_key(|r| r.relative_path);
130215
assert_eq!(records_to_tuple(recorder.records), expected_status);
@@ -256,6 +341,8 @@ fn replace_dir_with_file() {
256341
],
257342
|_| {},
258343
false,
344+
Default::default(),
345+
false,
259346
);
260347
assert_eq!(
261348
out,
@@ -453,6 +540,28 @@ fn unchanged() {
453540
fixture("status_unchanged", &[]);
454541
}
455542

543+
#[test]
544+
fn unchanged_despite_filter() {
545+
let actual_outcome = fixture_filtered_detailed(
546+
"status_unchanged_filter",
547+
"",
548+
&[],
549+
&[],
550+
|_| {},
551+
false,
552+
AutoCrlf::Enabled,
553+
true, /* make ODB available */
554+
);
555+
556+
let expected_outcome = Outcome {
557+
entries_to_process: 5,
558+
entries_processed: 5,
559+
symlink_metadata_calls: 5,
560+
..Default::default()
561+
};
562+
assert_eq!(actual_outcome, expected_outcome,);
563+
}
564+
456565
#[test]
457566
fn refresh() {
458567
let expected_outcome = Outcome {

gix/src/status/iter/mod.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -238,14 +238,22 @@ impl Iterator for Iter {
238238
#[cfg(feature = "parallel")]
239239
loop {
240240
let (rx, _join_worktree, _join_tree) = self.rx_and_join.as_ref()?;
241-
match rx.recv().ok() {
242-
Some(item) => {
241+
match rx.recv_timeout(std::time::Duration::from_millis(25)) {
242+
Ok(item) => {
243243
if let Some(item) = self.maybe_keep_index_change(item) {
244244
break Some(Ok(item));
245245
}
246246
continue;
247247
}
248-
None => {
248+
// NOTE: this isn't necessary when index::from-tree also supports interrupts. As it stands,
249+
// on big repositories it can go up to 500ms which aren't interruptible, so this is another
250+
// way to not wait for this. Once it can be interrupted, this won't be needed anymore.
251+
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
252+
if self.should_interrupt.load(Ordering::SeqCst) {
253+
return None;
254+
}
255+
}
256+
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
249257
let (_rx, worktree_handle, tree_handle) = self.rx_and_join.take()?;
250258
let tree_index = if let Some(handle) = tree_handle {
251259
match handle.join().expect("no panic") {

gix/src/util.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,10 @@ pub fn parallel_iter_drop<T, U, V>(
7575
return;
7676
}
7777
};
78-
// Wait until there is time to respond before we undo the change.
79-
if let Some(handle) = maybe_handle {
80-
handle.join().ok();
81-
}
82-
handle.join().ok();
78+
// Do not for the remaining threads. Everything but index-from-tree is interruptible, and that wouldn't
79+
// take very long even with huge trees.
80+
// If this every becomes a problem, just make `index::from-tree` interruptible, and keep waiting for handles here.
81+
drop((maybe_handle, handle));
8382
undo.fetch_update(
8483
std::sync::atomic::Ordering::SeqCst,
8584
std::sync::atomic::Ordering::SeqCst,

0 commit comments

Comments
 (0)