summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Gallant <jamslam@gmail.com>2019-01-26 15:42:55 -0500
committerAndrew Gallant <jamslam@gmail.com>2019-01-26 15:44:49 -0500
commitf3164f2615ce18d3ea7b5ce122dfe2a381d1b3f4 (patch)
treedc67e0cfec9a55236526ee9dac0aac96255dca17 /src
parent31d3e241306f305c1cb94e1882511da2b48dcd36 (diff)
exit: tweak exit status logic
This changes how ripgrep emit exit status codes. In particular, any error that occurs while searching will now cause ripgrep to emit a `2` exit code, where as it previously would emit either a `0` or a `1` code based on whether it matched or not. That is, ripgrep would only emit a `2` exit code for a catastrophic error. This tweak includes additional logic that GNU grep adheres to, which seems like good sense. Namely, if -q/--quiet is given, and an error occurs and a match occurs, then ripgrep will emit a `0` exit code. Closes #1159
Diffstat (limited to 'src')
-rw-r--r--src/args.rs5
-rw-r--r--src/main.rs50
-rw-r--r--src/messages.rs24
-rw-r--r--src/subject.rs16
4 files changed, 69 insertions, 26 deletions
diff --git a/src/args.rs b/src/args.rs
index cec3bca1..fed8ea60 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -268,6 +268,11 @@ impl Args {
Ok(builder.build(wtr))
}
+ /// Returns true if and only if ripgrep should be "quiet."
+ pub fn quiet(&self) -> bool {
+ self.matches().is_present("quiet")
+ }
+
/// Returns true if and only if the search should quit after finding the
/// first match.
pub fn quit_after_match(&self) -> Result<bool> {
diff --git a/src/main.rs b/src/main.rs
index 274a1fe2..63dab998 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -22,33 +22,37 @@ mod subject;
type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>;
fn main() {
- match Args::parse().and_then(try_main) {
- Ok(true) => process::exit(0),
- Ok(false) => process::exit(1),
- Err(err) => {
- eprintln!("{}", err);
- process::exit(2);
- }
+ if let Err(err) = Args::parse().and_then(try_main) {
+ eprintln!("{}", err);
+ process::exit(2);
}
}
-fn try_main(args: Args) -> Result<bool> {
+fn try_main(args: Args) -> Result<()> {
use args::Command::*;
- match args.command()? {
- Search => search(args),
- SearchParallel => search_parallel(args),
- SearchNever => Ok(false),
- Files => files(args),
- FilesParallel => files_parallel(args),
- Types => types(args),
+ let matched =
+ match args.command()? {
+ Search => search(&args),
+ SearchParallel => search_parallel(&args),
+ SearchNever => Ok(false),
+ Files => files(&args),
+ FilesParallel => files_parallel(&args),
+ Types => types(&args),
+ }?;
+ if matched && (args.quiet() || !messages::errored()) {
+ process::exit(0)
+ } else if messages::errored() {
+ process::exit(2)
+ } else {
+ process::exit(1)
}
}
/// The top-level entry point for single-threaded search. This recursively
/// steps through the file list (current directory by default) and searches
/// each file sequentially.
-fn search(args: Args) -> Result<bool> {
+fn search(args: &Args) -> Result<bool> {
let started_at = Instant::now();
let quit_after_match = args.quit_after_match()?;
let subject_builder = args.subject_builder();
@@ -68,7 +72,7 @@ fn search(args: Args) -> Result<bool> {
if err.kind() == io::ErrorKind::BrokenPipe {
break;
}
- message!("{}: {}", subject.path().display(), err);
+ err_message!("{}: {}", subject.path().display(), err);
continue;
}
};
@@ -91,7 +95,7 @@ fn search(args: Args) -> Result<bool> {
/// The top-level entry point for multi-threaded search. The parallelism is
/// itself achieved by the recursive directory traversal. All we need to do is
/// feed it a worker for performing a search on each file.
-fn search_parallel(args: Args) -> Result<bool> {
+fn search_parallel(args: &Args) -> Result<bool> {
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::SeqCst;
@@ -127,7 +131,7 @@ fn search_parallel(args: Args) -> Result<bool> {
let search_result = match searcher.search(&subject) {
Ok(search_result) => search_result,
Err(err) => {
- message!("{}: {}", subject.path().display(), err);
+ err_message!("{}: {}", subject.path().display(), err);
return WalkState::Continue;
}
};
@@ -144,7 +148,7 @@ fn search_parallel(args: Args) -> Result<bool> {
return WalkState::Quit;
}
// Otherwise, we continue on our merry way.
- message!("{}: {}", subject.path().display(), err);
+ err_message!("{}: {}", subject.path().display(), err);
}
if matched.load(SeqCst) && quit_after_match {
WalkState::Quit
@@ -169,7 +173,7 @@ fn search_parallel(args: Args) -> Result<bool> {
/// The top-level entry point for listing files without searching them. This
/// recursively steps through the file list (current directory by default) and
/// prints each path sequentially using a single thread.
-fn files(args: Args) -> Result<bool> {
+fn files(args: &Args) -> Result<bool> {
let quit_after_match = args.quit_after_match()?;
let subject_builder = args.subject_builder();
let mut matched = false;
@@ -199,7 +203,7 @@ fn files(args: Args) -> Result<bool> {
/// The top-level entry point for listing files without searching them. This
/// recursively steps through the file list (current directory by default) and
/// prints each path sequentially using multiple threads.
-fn files_parallel(args: Args) -> Result<bool> {
+fn files_parallel(args: &Args) -> Result<bool> {
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::SeqCst;
use std::sync::mpsc;
@@ -251,7 +255,7 @@ fn files_parallel(args: Args) -> Result<bool> {
}
/// The top-level entry point for --type-list.
-fn types(args: Args) -> Result<bool> {
+fn types(args: &Args) -> Result<bool> {
let mut count = 0;
let mut stdout = args.stdout();
for def in args.type_defs()? {
diff --git a/src/messages.rs b/src/messages.rs
index 21ca109d..9d134a14 100644
--- a/src/messages.rs
+++ b/src/messages.rs
@@ -2,7 +2,9 @@ use std::sync::atomic::{ATOMIC_BOOL_INIT, AtomicBool, Ordering};
static MESSAGES: AtomicBool = ATOMIC_BOOL_INIT;
static IGNORE_MESSAGES: AtomicBool = ATOMIC_BOOL_INIT;
+static ERRORED: AtomicBool = ATOMIC_BOOL_INIT;
+/// Emit a non-fatal error message, unless messages were disabled.
#[macro_export]
macro_rules! message {
($($tt:tt)*) => {
@@ -12,6 +14,18 @@ macro_rules! message {
}
}
+/// Like message, but sets ripgrep's "errored" flag, which controls the exit
+/// status.
+#[macro_export]
+macro_rules! err_message {
+ ($($tt:tt)*) => {
+ crate::messages::set_errored();
+ message!($($tt)*);
+ }
+}
+
+/// Emit a non-fatal ignore-related error message (like a parse error), unless
+/// ignore-messages were disabled.
#[macro_export]
macro_rules! ignore_message {
($($tt:tt)*) => {
@@ -48,3 +62,13 @@ pub fn ignore_messages() -> bool {
pub fn set_ignore_messages(yes: bool) {
IGNORE_MESSAGES.store(yes, Ordering::SeqCst)
}
+
+/// Returns true if and only if ripgrep came across a non-fatal error.
+pub fn errored() -> bool {
+ ERRORED.load(Ordering::SeqCst)
+}
+
+/// Indicate that ripgrep has come across a non-fatal error.
+pub fn set_errored() {
+ ERRORED.store(true, Ordering::SeqCst);
+}
diff --git a/src/subject.rs b/src/subject.rs
index 82cddbd9..0eae5c26 100644
--- a/src/subject.rs
+++ b/src/subject.rs
@@ -41,7 +41,7 @@ impl SubjectBuilder {
match result {
Ok(dent) => self.build(dent),
Err(err) => {
- message!("{}", err);
+ err_message!("{}", err);
None
}
}
@@ -127,9 +127,19 @@ impl Subject {
self.dent.is_stdin()
}
- /// Returns true if and only if this subject points to a directory.
+ /// Returns true if and only if this subject points to a directory after
+ /// following symbolic links.
fn is_dir(&self) -> bool {
- self.dent.file_type().map_or(false, |ft| ft.is_dir())
+ let ft = match self.dent.file_type() {
+ None => return false,
+ Some(ft) => ft,
+ };
+ if ft.is_dir() {
+ return true;
+ }
+ // If this is a symlink, then we want to follow it to determine
+ // whether it's a directory or not.
+ self.dent.path_is_symlink() && self.dent.path().is_dir()
}
/// Returns true if and only if this subject points to a file.