Skip to content

Commit 37c3d07

Browse files
committed
add status.showUntrackedFiles to config-tree and use it in status()
1 parent f8ce3d0 commit 37c3d07

File tree

13 files changed

+270
-20
lines changed

13 files changed

+270
-20
lines changed

Cargo.lock

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

gix/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ parking_lot = "0.12.1"
323323
document-features = { version = "0.2.0", optional = true }
324324

325325
[dev-dependencies]
326+
pretty_assertions = "1.4.0"
326327
gix-testtools = { path = "../tests/tools" }
327328
is_ci = "1.1.1"
328329
anyhow = "1"

gix/src/config/tree/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ pub(crate) mod root {
5959
pub const SAFE: sections::Safe = sections::Safe;
6060
/// The `ssh` section.
6161
pub const SSH: sections::Ssh = sections::Ssh;
62+
/// The `status` section.
63+
pub const STATUS: sections::Status = sections::Status;
6264
/// The `user` section.
6365
pub const USER: sections::User = sections::User;
6466
/// The `url` section.
@@ -89,6 +91,7 @@ pub(crate) mod root {
8991
&Self::REMOTE,
9092
&Self::SAFE,
9193
&Self::SSH,
94+
&Self::STATUS,
9295
&Self::USER,
9396
&Self::URL,
9497
]
@@ -98,9 +101,9 @@ pub(crate) mod root {
98101

99102
mod sections;
100103
pub use sections::{
101-
branch, checkout, core, credential, extensions, fetch, gitoxide, http, index, protocol, push, remote, ssh, Author,
102-
Branch, Checkout, Clone, Committer, Core, Credential, Extensions, Fetch, Gitoxide, Http, Index, Init, Mailmap,
103-
Pack, Protocol, Push, Remote, Safe, Ssh, Url, User,
104+
branch, checkout, core, credential, extensions, fetch, gitoxide, http, index, protocol, push, remote, ssh, status,
105+
Author, Branch, Checkout, Clone, Committer, Core, Credential, Extensions, Fetch, Gitoxide, Http, Index, Init,
106+
Mailmap, Pack, Protocol, Push, Remote, Safe, Ssh, Status, Url, User,
104107
};
105108
#[cfg(feature = "blob-diff")]
106109
pub use sections::{diff, Diff};

gix/src/config/tree/sections/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ mod safe;
106106
pub struct Ssh;
107107
pub mod ssh;
108108

109+
/// The `status` top-level section.
110+
#[derive(Copy, Clone, Default)]
111+
pub struct Status;
112+
pub mod status;
113+
109114
/// The `user` top-level section.
110115
#[derive(Copy, Clone, Default)]
111116
pub struct User;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use crate::config;
2+
use crate::config::tree::sections::Status;
3+
use crate::config::tree::{keys, Key, Section};
4+
5+
impl Status {
6+
/// The `status.showUntrackedFiles` key
7+
pub const SHOW_UNTRACKED_FILES: ShowUntrackedFiles = ShowUntrackedFiles::new_with_validate(
8+
"showUntrackedFiles",
9+
&config::Tree::STATUS,
10+
validate::ShowUntrackedFiles,
11+
);
12+
}
13+
14+
/// The `status.showUntrackedFiles` key.
15+
pub type ShowUntrackedFiles = keys::Any<validate::ShowUntrackedFiles>;
16+
17+
mod show_untracked_files {
18+
use std::borrow::Cow;
19+
20+
use crate::{bstr::BStr, config, config::tree::status::ShowUntrackedFiles, status};
21+
22+
impl ShowUntrackedFiles {
23+
pub fn try_into_show_untracked_files(
24+
&'static self,
25+
value: Cow<'_, BStr>,
26+
) -> Result<status::UntrackedFiles, config::key::GenericErrorWithValue> {
27+
use crate::bstr::ByteSlice;
28+
Ok(match value.as_ref().as_bytes() {
29+
b"no" => status::UntrackedFiles::None,
30+
b"normal" => status::UntrackedFiles::Collapsed,
31+
b"all" => status::UntrackedFiles::Files,
32+
_ => return Err(config::key::GenericErrorWithValue::from_value(self, value.into_owned())),
33+
})
34+
}
35+
}
36+
}
37+
38+
impl Section for Status {
39+
fn name(&self) -> &str {
40+
"status"
41+
}
42+
43+
fn keys(&self) -> &[&dyn Key] {
44+
&[&Self::SHOW_UNTRACKED_FILES]
45+
}
46+
}
47+
48+
mod validate {
49+
use crate::{bstr::BStr, config::tree::keys};
50+
51+
pub struct ShowUntrackedFiles;
52+
impl keys::Validate for ShowUntrackedFiles {
53+
fn validate(&self, value: &BStr) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
54+
super::Status::SHOW_UNTRACKED_FILES.try_into_show_untracked_files(value.into())?;
55+
Ok(())
56+
}
57+
}
58+
}

gix/src/status/mod.rs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::config::cache::util::ApplyLeniencyDefault;
12
use crate::{config, Repository};
23
pub use gix_status as plumbing;
34
use std::ops::Deref;
@@ -71,23 +72,52 @@ pub enum Submodule {
7172
},
7273
}
7374

75+
/// How untracked files should be handled.
76+
#[derive(Default, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
77+
pub enum UntrackedFiles {
78+
/// Do not show any untracked files.
79+
///
80+
/// This can mean no directory walk is performed.
81+
None,
82+
/// If possible, collapse files into their parent folders to reduce the amount of
83+
/// emitted untracked files.
84+
#[default]
85+
Collapsed,
86+
/// Show each individual untracked file or directory (if empty directories are emitted) that the dirwalk encountered .
87+
Files,
88+
}
89+
7490
impl Default for Submodule {
7591
fn default() -> Self {
7692
Submodule::AsConfigured { check_dirty: false }
7793
}
7894
}
7995

96+
/// The error returned by [status()](Repository::status).
97+
#[derive(Debug, thiserror::Error)]
98+
#[allow(missing_docs)]
99+
pub enum Error {
100+
#[error(transparent)]
101+
DirwalkOptions(#[from] config::boolean::Error),
102+
#[error(transparent)]
103+
ConfigureUntrackedFiles(#[from] config::key::GenericErrorWithValue),
104+
}
105+
80106
/// Status
81107
impl Repository {
82108
/// Obtain a platform for configuring iterators for traversing git repository status information.
83109
///
84110
/// By default, this is set to the fastest and most immediate way of obtaining a status,
85111
/// which is most similar to
86112
///
87-
/// `git status --untracked=all --ignored=no --no-renames`
113+
/// `git status --ignored=no`
88114
///
89115
/// which implies that submodule information is provided by default.
90116
///
117+
/// Note that `status.showUntrackedFiles` is respected, which leads to untracked files being
118+
/// collapsed by default. If that needs to be controlled,
119+
/// [configure the directory walk explicitly](Platform::dirwalk_options) or more [implicitly](Platform::untracked_files).
120+
///
91121
/// Pass `progress` to receive progress information on file modifications on this repository.
92122
/// Use [`progress::Discard`](crate::progress::Discard) to discard all progress information.
93123
///
@@ -96,11 +126,11 @@ impl Repository {
96126
/// Whereas Git runs the index-modified check before the directory walk to set entries
97127
/// as up-to-date to (potentially) safe some disk-access, we run both in parallel which
98128
/// ultimately is much faster.
99-
pub fn status<P>(&self, progress: P) -> Result<Platform<'_, P>, config::boolean::Error>
129+
pub fn status<P>(&self, progress: P) -> Result<Platform<'_, P>, Error>
100130
where
101131
P: gix_features::progress::Progress + 'static,
102132
{
103-
Ok(Platform {
133+
let platform = Platform {
104134
repo: self,
105135
progress,
106136
index: None,
@@ -112,7 +142,20 @@ impl Repository {
112142
rewrites: None,
113143
thread_limit: None,
114144
},
115-
})
145+
};
146+
147+
let untracked = self
148+
.config
149+
.resolved
150+
.string("status", None, "showUntrackedFiles")
151+
.map(|value| {
152+
config::tree::Status::SHOW_UNTRACKED_FILES
153+
.try_into_show_untracked_files(value)
154+
.with_lenient_default(self.config.lenient_config)
155+
})
156+
.transpose()?
157+
.unwrap_or_default();
158+
Ok(platform.untracked_files(untracked))
116159
}
117160
}
118161

@@ -126,7 +169,7 @@ pub mod is_dirty {
126169
#[allow(missing_docs)]
127170
pub enum Error {
128171
#[error(transparent)]
129-
StatusPlatform(#[from] crate::config::boolean::Error),
172+
StatusPlatform(#[from] crate::status::Error),
130173
#[error(transparent)]
131174
CreateStatusIterator(#[from] crate::status::index_worktree::iter::Error),
132175
}

gix/src/status/platform.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::status::{index_worktree, OwnedOrStaticAtomic, Platform, Submodule};
1+
use crate::status::{index_worktree, OwnedOrStaticAtomic, Platform, Submodule, UntrackedFiles};
22
use std::sync::atomic::AtomicBool;
33

44
/// Builder
@@ -17,6 +17,23 @@ where
1717
self
1818
}
1919

20+
/// A simple way to explicitly set the desired way of listing `untracked_files`, overriding any value
21+
/// set by the git configuration.
22+
///
23+
/// Note that if [`None`](UntrackedFiles::None) is used, the directory walk will be disabled entirely
24+
/// after this call. Further, if no dirwalk options are present anymore, this call has no effect.
25+
pub fn untracked_files(mut self, untracked_files: UntrackedFiles) -> Self {
26+
let mode = match untracked_files {
27+
UntrackedFiles::None => {
28+
self.index_worktree_options.dirwalk_options.take();
29+
return self;
30+
}
31+
UntrackedFiles::Collapsed => gix_dir::walk::EmissionMode::CollapseDirectory,
32+
UntrackedFiles::Files => gix_dir::walk::EmissionMode::Matching,
33+
};
34+
self.dirwalk_options(|cb| cb.emit_untracked(mode))
35+
}
36+
2037
/// Set the interrupt flag to `should_interrupt`, which typically is an application-wide flag
2138
/// that is ultimately controlled by user interrupts.
2239
///

gix/src/submodule/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ pub mod status {
298298
#[error(transparent)]
299299
IgnoreConfiguration(#[from] config::Error),
300300
#[error(transparent)]
301-
StatusPlatform(#[from] crate::config::boolean::Error),
301+
StatusPlatform(#[from] crate::status::Error),
302302
#[error(transparent)]
303303
Status(#[from] crate::status::index_worktree::iter::Error),
304304
#[error(transparent)]
@@ -384,7 +384,6 @@ pub mod status {
384384

385385
let statusses = adjust_options(sm_repo.status(gix_features::progress::Discard)?)
386386
.index_worktree_options_mut(|opts| {
387-
assert!(opts.dirwalk_options.is_some(), "BUG: it's supposed to be the default");
388387
if ignore == config::Ignore::Untracked {
389388
opts.dirwalk_options = None;
390389
}

gix/tests/config/tree.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,36 @@ mod ssh {
145145
}
146146
}
147147

148+
mod status {
149+
use crate::config::tree::bcow;
150+
use gix::config::tree::Status;
151+
use gix::status::UntrackedFiles;
152+
153+
#[test]
154+
fn default() -> crate::Result {
155+
for (actual, expected) in [
156+
("no", UntrackedFiles::None),
157+
("normal", UntrackedFiles::Collapsed),
158+
("all", UntrackedFiles::Files),
159+
] {
160+
assert_eq!(
161+
Status::SHOW_UNTRACKED_FILES.try_into_show_untracked_files(bcow(actual))?,
162+
expected
163+
);
164+
}
165+
166+
assert_eq!(
167+
Status::SHOW_UNTRACKED_FILES
168+
.try_into_show_untracked_files(bcow("NO"))
169+
.unwrap_err()
170+
.to_string(),
171+
"The key \"status.showUntrackedFiles=NO\" was invalid",
172+
"case-sensitive comparisons"
173+
);
174+
Ok(())
175+
}
176+
}
177+
148178
mod push {
149179
use crate::config::tree::bcow;
150180
use gix::config::tree::Push;
Binary file not shown.
Binary file not shown.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/bash
2+
set -eu -o pipefail
3+
4+
git init -q untracked-only
5+
(cd untracked-only
6+
touch this
7+
mkdir subdir
8+
>subdir/that
9+
10+
git add .
11+
git commit -q -m init
12+
13+
mkdir new
14+
touch new/untracked subdir/untracked
15+
)
16+

0 commit comments

Comments
 (0)