Skip to content

Commit

Permalink
Allow reading patterns from stdin (#301)
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Apr 30, 2022
1 parent 7697f51 commit 0c597fe
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 29 deletions.
30 changes: 16 additions & 14 deletions git-path/src/spec.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
use crate::Spec;
use bstr::{BStr, ByteSlice, ByteVec};
use std::ffi::OsStr;
use std::str::FromStr;

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

fn try_from(value: &OsStr) -> Result<Self, Self::Error> {
crate::os_str_into_bstr(value).map(|value| Spec(value.into()))
crate::os_str_into_bstr(value).map(|value| {
assert_valid_hack(value);
Spec(value.into())
})
}
}

impl FromStr for Spec {
type Err = std::convert::Infallible;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Spec(s.into()))
}
fn assert_valid_hack(input: &BStr) {
assert!(!input.contains_str(b"/../"));
assert!(!input.contains_str(b"/./"));
assert!(!input.starts_with_str(b"../"));
assert!(!input.starts_with_str(b"./"));
assert!(!input.starts_with_str(b"/"));
}

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

let prefix = crate::into_bstr(prefix);
if !prefix.is_empty() {
let mut prefix = crate::to_unix_separators_on_windows(prefix);
Expand Down
4 changes: 1 addition & 3 deletions gitoxide-core/src/repository/exclude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,22 @@ use git_repository::prelude::FindExt;

pub mod query {
use crate::OutputFormat;
use git_repository as git;
use std::ffi::OsString;

pub struct Options {
pub format: OutputFormat,
pub pathspecs: Vec<git::path::Spec>,
pub overrides: Vec<OsString>,
pub show_ignore_patterns: bool,
}
}

pub fn query(
repo: git::Repository,
pathspecs: impl Iterator<Item = git::path::Spec>,
mut out: impl io::Write,
query::Options {
overrides,
format,
pathspecs,
show_ignore_patterns,
}: query::Options,
) -> anyhow::Result<()> {
Expand Down
32 changes: 21 additions & 11 deletions src/plumbing/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::{

use anyhow::Result;
use clap::Parser;
use git_repository::bstr::io::BufReadExt;
use gitoxide_core as core;
use gitoxide_core::pack::verify;

Expand Down Expand Up @@ -206,12 +207,23 @@ pub fn main() -> Result<()> {
progress_keep_open,
None,
move |_progress, out, _err| {
use git::bstr::ByteSlice;
core::repository::exclude::query(
repository.into(),
if pathspecs.is_empty() {
Box::new(
stdin_or_bail()?
.byte_lines()
.filter_map(Result::ok)
.map(|line| git::path::Spec::from_bytes(line.as_bstr()))
.flatten(),
) as Box<dyn Iterator<Item = git::path::Spec>>
} else {
Box::new(pathspecs.into_iter())
},
out,
core::repository::exclude::query::Options {
format,
pathspecs,
show_ignore_patterns,
overrides: patterns,
},
Expand Down Expand Up @@ -341,16 +353,7 @@ pub fn main() -> Result<()> {
progress_keep_open,
core::pack::create::PROGRESS_RANGE,
move |progress, out, _err| {
let input = if has_tips {
None
} else {
if atty::is(atty::Stream::Stdin) {
anyhow::bail!(
"Refusing to read from standard input as no path is given, but it's a terminal."
)
}
Some(BufReader::new(stdin()))
};
let input = if has_tips { None } else { stdin_or_bail()?.into() };
let repository = repository.unwrap_or_else(|| PathBuf::from("."));
let context = core::pack::create::Context {
thread_limit,
Expand Down Expand Up @@ -641,6 +644,13 @@ pub fn main() -> Result<()> {
Ok(())
}

fn stdin_or_bail() -> anyhow::Result<std::io::BufReader<std::io::Stdin>> {
if atty::is(atty::Stream::Stdin) {
anyhow::bail!("Refusing to read from standard input while a terminal is connected")
}
Ok(BufReader::new(stdin()))
}

fn verify_mode(decode: bool, re_encode: bool) -> verify::Mode {
match (decode, re_encode) {
(true, false) => verify::Mode::HashCrc32Decode,
Expand Down
2 changes: 1 addition & 1 deletion src/plumbing/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ pub mod repo {
/// Useful for undoing previous patterns using the '!' prefix.
#[clap(long, short = 'p')]
patterns: Vec<OsString>,
/// The git path specifications to check for exclusion.
/// The git path specifications to check for exclusion, or unset to read from stdin one per line.
#[clap(parse(try_from_os_str = std::convert::TryFrom::try_from))]
pathspecs: Vec<git::path::Spec>,
},
Expand Down

0 comments on commit 0c597fe

Please sign in to comment.