cli: make ripgrep work in non-existent directories

It turns out that querying the CWD while in a directory that no longer
exists results in an error. Since the CWD is queried every time ripgrep
starts---whether it needs it or not---for dealing with glob matching,
ripgrep winds up being completely useless inside a non-existent
directory.

We fix this in a few different ways:

* Firstly, if std::env::current_dir() fails, then we fall back to trying
  to read the `PWD` environment variable.
* If that fails, that we return a more sensible error message so that a
  user can at least react to the problem. Previously, the error message
  was inscrutable.
* Finally, we try to avoid the problem altogether by building empty glob
  matchers if not globs were provided, thus side-stepping querying the
  CWD completely.

Fixes #1291, Closes #1400
This commit is contained in:
Andrew Gallant 2020-02-16 21:03:07 -05:00
parent 297b428c8c
commit 0c3b673e4c
2 changed files with 39 additions and 5 deletions

View File

@ -28,6 +28,8 @@ Feature enhancements:
Bug fixes: Bug fixes:
* [BUG #1291](https://github.com/BurntSushi/ripgrep/issues/1291):
ripgrep now works in non-existent directories.
* [BUG #1335](https://github.com/BurntSushi/ripgrep/issues/1335): * [BUG #1335](https://github.com/BurntSushi/ripgrep/issues/1335):
Fixes a performance bug when searching plain text files with very long lines. Fixes a performance bug when searching plain text files with very long lines.
* [BUG #1344](https://github.com/BurntSushi/ripgrep/issues/1344): * [BUG #1344](https://github.com/BurntSushi/ripgrep/issues/1344):

View File

@ -1277,17 +1277,23 @@ impl ArgMatches {
/// Builds the set of glob overrides from the command line flags. /// Builds the set of glob overrides from the command line flags.
fn overrides(&self) -> Result<Override> { fn overrides(&self) -> Result<Override> {
let mut builder = OverrideBuilder::new(env::current_dir()?); let globs = self.values_of_lossy_vec("glob");
let iglobs = self.values_of_lossy_vec("iglob");
if globs.is_empty() && iglobs.is_empty() {
return Ok(Override::empty());
}
let mut builder = OverrideBuilder::new(current_dir()?);
// Make all globs case insensitive with --glob-case-insensitive. // Make all globs case insensitive with --glob-case-insensitive.
if self.is_present("glob-case-insensitive") { if self.is_present("glob-case-insensitive") {
builder.case_insensitive(true).unwrap(); builder.case_insensitive(true).unwrap();
} }
for glob in self.values_of_lossy_vec("glob") { for glob in globs {
builder.add(&glob)?; builder.add(&glob)?;
} }
// This only enables case insensitivity for subsequent globs. // This only enables case insensitivity for subsequent globs.
builder.case_insensitive(true).unwrap(); builder.case_insensitive(true).unwrap();
for glob in self.values_of_lossy_vec("iglob") { for glob in iglobs {
builder.add(&glob)?; builder.add(&glob)?;
} }
Ok(builder.build()?) Ok(builder.build()?)
@ -1489,8 +1495,12 @@ impl ArgMatches {
/// flag. If no --pre-globs are available, then this always returns an /// flag. If no --pre-globs are available, then this always returns an
/// empty set of globs. /// empty set of globs.
fn preprocessor_globs(&self) -> Result<Override> { fn preprocessor_globs(&self) -> Result<Override> {
let mut builder = OverrideBuilder::new(env::current_dir()?); let globs = self.values_of_lossy_vec("pre-glob");
for glob in self.values_of_lossy_vec("pre-glob") { if globs.is_empty() {
return Ok(Override::empty());
}
let mut builder = OverrideBuilder::new(current_dir()?);
for glob in globs {
builder.add(&glob)?; builder.add(&glob)?;
} }
Ok(builder.build()?) Ok(builder.build()?)
@ -1794,3 +1804,25 @@ where I: IntoIterator<Item=T>,
let _ = write!(io::stdout(), "{}", err); let _ = write!(io::stdout(), "{}", err);
process::exit(0); process::exit(0);
} }
/// Attempts to discover the current working directory. This mostly just defers
/// to the standard library, however, such things will fail if ripgrep is in
/// a directory that no longer exists. We attempt some fallback mechanisms,
/// such as querying the PWD environment variable, but otherwise return an
/// error.
fn current_dir() -> Result<PathBuf> {
let err = match env::current_dir() {
Err(err) => err,
Ok(cwd) => return Ok(cwd),
};
if let Some(cwd) = env::var_os("PWD") {
if !cwd.is_empty() {
return Ok(PathBuf::from(cwd));
}
}
Err(format!(
"failed to get current working directory: {} \
--- did your CWD get deleted?",
err,
).into())
}