Skip to content

Commit 0c597fe

Browse files
committed
Allow reading patterns from stdin (#301)
1 parent 7697f51 commit 0c597fe

File tree

4 files changed

+39
-29
lines changed

4 files changed

+39
-29
lines changed

Diff for: git-path/src/spec.rs

+16-14
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,41 @@
11
use crate::Spec;
22
use bstr::{BStr, ByteSlice, ByteVec};
33
use std::ffi::OsStr;
4-
use std::str::FromStr;
54

65
impl std::convert::TryFrom<&OsStr> for Spec {
76
type Error = crate::Utf8Error;
87

98
fn try_from(value: &OsStr) -> Result<Self, Self::Error> {
10-
crate::os_str_into_bstr(value).map(|value| Spec(value.into()))
9+
crate::os_str_into_bstr(value).map(|value| {
10+
assert_valid_hack(value);
11+
Spec(value.into())
12+
})
1113
}
1214
}
1315

14-
impl FromStr for Spec {
15-
type Err = std::convert::Infallible;
16-
17-
fn from_str(s: &str) -> Result<Self, Self::Err> {
18-
Ok(Spec(s.into()))
19-
}
16+
fn assert_valid_hack(input: &BStr) {
17+
assert!(!input.contains_str(b"/../"));
18+
assert!(!input.contains_str(b"/./"));
19+
assert!(!input.starts_with_str(b"../"));
20+
assert!(!input.starts_with_str(b"./"));
21+
assert!(!input.starts_with_str(b"/"));
2022
}
2123

2224
impl Spec {
25+
/// Parse `input` into a `Spec` or `None` if it could not be parsed
26+
// TODO: tests, actual implementation probably via `git-pathspec` to make use of the crate after all.
27+
pub fn from_bytes(input: &BStr) -> Option<Self> {
28+
assert_valid_hack(input);
29+
Spec(input.into()).into()
30+
}
2331
/// Return all paths described by this path spec, using slashes on all platforms.
2432
pub fn items(&self) -> impl Iterator<Item = &BStr> {
2533
std::iter::once(self.0.as_bstr())
2634
}
2735
/// Adjust this path specification according to the given `prefix`, which may be empty to indicate we are the at work-tree root.
2836
// TODO: this is a hack, needs test and time to do according to spec. This is just a minimum version to have -something-.
2937
pub fn apply_prefix(&mut self, prefix: &std::path::Path) -> &Self {
30-
assert!(!self.0.contains_str(b"/../"));
31-
assert!(!self.0.contains_str(b"/./"));
32-
assert!(!self.0.starts_with_str(b"../"));
33-
assert!(!self.0.starts_with_str(b"./"));
34-
assert!(!self.0.starts_with_str(b"/"));
3538
// many more things we can't handle. `Path` never ends with trailing path separator.
36-
3739
let prefix = crate::into_bstr(prefix);
3840
if !prefix.is_empty() {
3941
let mut prefix = crate::to_unix_separators_on_windows(prefix);

Diff for: gitoxide-core/src/repository/exclude.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,22 @@ use git_repository::prelude::FindExt;
77

88
pub mod query {
99
use crate::OutputFormat;
10-
use git_repository as git;
1110
use std::ffi::OsString;
1211

1312
pub struct Options {
1413
pub format: OutputFormat,
15-
pub pathspecs: Vec<git::path::Spec>,
1614
pub overrides: Vec<OsString>,
1715
pub show_ignore_patterns: bool,
1816
}
1917
}
2018

2119
pub fn query(
2220
repo: git::Repository,
21+
pathspecs: impl Iterator<Item = git::path::Spec>,
2322
mut out: impl io::Write,
2423
query::Options {
2524
overrides,
2625
format,
27-
pathspecs,
2826
show_ignore_patterns,
2927
}: query::Options,
3028
) -> anyhow::Result<()> {

Diff for: src/plumbing/main.rs

+21-11
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::{
99

1010
use anyhow::Result;
1111
use clap::Parser;
12+
use git_repository::bstr::io::BufReadExt;
1213
use gitoxide_core as core;
1314
use gitoxide_core::pack::verify;
1415

@@ -206,12 +207,23 @@ pub fn main() -> Result<()> {
206207
progress_keep_open,
207208
None,
208209
move |_progress, out, _err| {
210+
use git::bstr::ByteSlice;
209211
core::repository::exclude::query(
210212
repository.into(),
213+
if pathspecs.is_empty() {
214+
Box::new(
215+
stdin_or_bail()?
216+
.byte_lines()
217+
.filter_map(Result::ok)
218+
.map(|line| git::path::Spec::from_bytes(line.as_bstr()))
219+
.flatten(),
220+
) as Box<dyn Iterator<Item = git::path::Spec>>
221+
} else {
222+
Box::new(pathspecs.into_iter())
223+
},
211224
out,
212225
core::repository::exclude::query::Options {
213226
format,
214-
pathspecs,
215227
show_ignore_patterns,
216228
overrides: patterns,
217229
},
@@ -341,16 +353,7 @@ pub fn main() -> Result<()> {
341353
progress_keep_open,
342354
core::pack::create::PROGRESS_RANGE,
343355
move |progress, out, _err| {
344-
let input = if has_tips {
345-
None
346-
} else {
347-
if atty::is(atty::Stream::Stdin) {
348-
anyhow::bail!(
349-
"Refusing to read from standard input as no path is given, but it's a terminal."
350-
)
351-
}
352-
Some(BufReader::new(stdin()))
353-
};
356+
let input = if has_tips { None } else { stdin_or_bail()?.into() };
354357
let repository = repository.unwrap_or_else(|| PathBuf::from("."));
355358
let context = core::pack::create::Context {
356359
thread_limit,
@@ -641,6 +644,13 @@ pub fn main() -> Result<()> {
641644
Ok(())
642645
}
643646

647+
fn stdin_or_bail() -> anyhow::Result<std::io::BufReader<std::io::Stdin>> {
648+
if atty::is(atty::Stream::Stdin) {
649+
anyhow::bail!("Refusing to read from standard input while a terminal is connected")
650+
}
651+
Ok(BufReader::new(stdin()))
652+
}
653+
644654
fn verify_mode(decode: bool, re_encode: bool) -> verify::Mode {
645655
match (decode, re_encode) {
646656
(true, false) => verify::Mode::HashCrc32Decode,

Diff for: src/plumbing/options.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ pub mod repo {
395395
/// Useful for undoing previous patterns using the '!' prefix.
396396
#[clap(long, short = 'p')]
397397
patterns: Vec<OsString>,
398-
/// The git path specifications to check for exclusion.
398+
/// The git path specifications to check for exclusion, or unset to read from stdin one per line.
399399
#[clap(parse(try_from_os_str = std::convert::TryFrom::try_from))]
400400
pathspecs: Vec<git::path::Spec>,
401401
},

0 commit comments

Comments
 (0)