diff --git a/CHANGELOG.md b/CHANGELOG.md index 30736139..13aadf7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ Bug fixes: This was a serious performance regression in some cases. * [BUG #1344](https://github.com/BurntSushi/ripgrep/issues/1344): Document usage of `--type all`. +* [BUG #1389](https://github.com/BurntSushi/ripgrep/issues/1389): + Fixes a bug where ripgrep would panic when searching a symlinked directory. * [BUG #1445](https://github.com/BurntSushi/ripgrep/issues/1445): ripgrep now respects ignore rules from .git/info/exclude in worktrees. * [BUG #1485](https://github.com/BurntSushi/ripgrep/issues/1485): diff --git a/ignore/src/walk.rs b/ignore/src/walk.rs index 331268a3..b430981f 100644 --- a/ignore/src/walk.rs +++ b/ignore/src/walk.rs @@ -1043,7 +1043,7 @@ impl Iterator for WalkEventIter { None => None, Some(Err(err)) => Some(Err(err)), Some(Ok(dent)) => { - if dent.file_type().is_dir() { + if walkdir_is_dir(&dent) { self.depth += 1; Some(Ok(WalkEvent::Dir(dent))) } else { @@ -1791,6 +1791,24 @@ fn path_equals(dent: &DirEntry, handle: &Handle) -> Result { .map_err(|err| Error::Io(err).with_path(dent.path())) } +/// Returns true if the given walkdir entry corresponds to a directory. +/// +/// This is normally just `dent.file_type().is_dir()`, but when we aren't +/// following symlinks, the root directory entry may be a symlink to a +/// directory that we *do* follow---by virtue of it being specified by the user +/// explicitly. In that case, we need to follow the symlink and query whether +/// it's a directory or not. But we only do this for root entries to avoid an +/// additional stat check in most cases. +fn walkdir_is_dir(dent: &walkdir::DirEntry) -> bool { + if dent.file_type().is_dir() { + return true; + } + if !dent.file_type().is_symlink() || dent.depth() > 0 { + return false; + } + dent.path().metadata().ok().map_or(false, |md| md.file_type().is_dir()) +} + /// Returns true if and only if the given path is on the same device as the /// given root device. fn is_same_file_system(root_device: u64, path: &Path) -> Result { diff --git a/tests/regression.rs b/tests/regression.rs index 72f58590..29f15a27 100644 --- a/tests/regression.rs +++ b/tests/regression.rs @@ -747,6 +747,21 @@ rgtest!(r1334_crazy_literals, |dir: Dir, mut cmd: TestCommand| { ); }); +// See: https://github.com/BurntSushi/ripgrep/issues/1389 +rgtest!(r1389_bad_symlinks_no_biscuit, |dir: Dir, mut cmd: TestCommand| { + dir.create_dir("mydir"); + dir.create("mydir/file.txt", "test"); + dir.link_dir("mydir", "mylink"); + + let stdout = cmd.args(&[ + "test", + "--no-ignore", + "--sort", "path", + "mylink", + ]).stdout(); + eqnice!("mylink/file.txt:test\n", stdout); +}); + // See: https://github.com/BurntSushi/ripgrep/pull/1446 rgtest!(r1446_respect_excludes_in_worktree, |dir: Dir, mut cmd: TestCommand| { dir.create_dir("repo/.git/info");