Skip to content

Commit 26756f2

Browse files
committed
refactor: cache cwd at startup instead of per-entry resolution
Instead of calling current_dir() for every entry when --full-path is set, cache it once in Config at startup. This eliminates the need for FatalError handling and makes path resolution infallible during the walk. If the cwd can't be retrieved, fd now fails early with a clear error message.
1 parent 24f7f2b commit 26756f2

5 files changed

Lines changed: 74 additions & 81 deletions

File tree

src/config.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ pub struct Config {
1515
/// Whether the search is case-sensitive or case-insensitive.
1616
pub case_sensitive: bool,
1717

18-
/// Whether to search within the full file path or just the base name (filename or directory
19-
/// name).
20-
pub search_full_path: bool,
18+
/// Cached current working directory for absolute path construction.
19+
/// Populated when `--full-path` is set; `None` means search by filename only.
20+
pub cwd: Option<PathBuf>,
2121

2222
/// Whether to ignore hidden files and directories (or not).
2323
pub ignore_hidden: bool,

src/exec/job.rs

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@ pub fn job(
2828
}
2929
continue;
3030
}
31-
WorkerResult::FatalError(err) => {
32-
if config.show_filesystem_errors {
33-
print_error(err.to_string());
34-
}
35-
return ExitCode::GeneralError;
36-
}
3731
};
3832

3933
// Generate a command, execute it and store its exit code.
@@ -54,28 +48,17 @@ pub fn batch(
5448
cmd: &CommandSet,
5549
config: &Config,
5650
) -> ExitCode {
57-
let mut paths = Vec::new();
58-
59-
for worker_result in results {
60-
match worker_result {
61-
WorkerResult::Entry(dir_entry) => paths.push(dir_entry.into_stripped_path(config)),
51+
let paths = results
52+
.into_iter()
53+
.filter_map(|worker_result| match worker_result {
54+
WorkerResult::Entry(dir_entry) => Some(dir_entry.into_stripped_path(config)),
6255
WorkerResult::Error(err) => {
6356
if config.show_filesystem_errors {
6457
print_error(err.to_string());
6558
}
59+
None
6660
}
67-
WorkerResult::FatalError(err) => {
68-
if config.show_filesystem_errors {
69-
print_error(err.to_string());
70-
}
71-
return ExitCode::GeneralError;
72-
}
73-
}
74-
}
61+
});
7562

76-
cmd.execute_batch(
77-
paths.into_iter(),
78-
config.batch_size,
79-
config.path_separator.as_deref(),
80-
)
63+
cmd.execute_batch(paths, config.batch_size, config.path_separator.as_deref())
8164
}

src/filesystem.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
2020
env::current_dir().map(|path_buf| path_buf.join(path))
2121
}
2222

23+
/// Construct an absolute path from a potentially relative path and a
24+
/// pre-resolved working directory. Unlike `path_absolute_form`, this
25+
/// does not call `env::current_dir()` and cannot fail.
26+
pub fn make_absolute(path: &Path, cwd: &Path) -> PathBuf {
27+
if path.is_absolute() {
28+
return path.to_path_buf();
29+
}
30+
let path = path.strip_prefix(".").unwrap_or(path);
31+
cwd.join(path)
32+
}
33+
2334
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
2435
let path_buf = path_absolute_form(path)?;
2536

@@ -153,4 +164,40 @@ mod tests {
153164
Path::new("foo/bar/baz")
154165
);
155166
}
167+
168+
#[test]
169+
fn make_absolute_with_relative_path() {
170+
use super::make_absolute;
171+
use std::path::PathBuf;
172+
173+
let cwd = Path::new("/home/user");
174+
assert_eq!(
175+
make_absolute(Path::new("foo/bar"), cwd),
176+
PathBuf::from("/home/user/foo/bar")
177+
);
178+
}
179+
180+
#[test]
181+
fn make_absolute_strips_dot_prefix() {
182+
use super::make_absolute;
183+
use std::path::PathBuf;
184+
185+
let cwd = Path::new("/home/user");
186+
assert_eq!(
187+
make_absolute(Path::new("./foo/bar"), cwd),
188+
PathBuf::from("/home/user/foo/bar")
189+
);
190+
}
191+
192+
#[test]
193+
fn make_absolute_with_absolute_path() {
194+
use super::make_absolute;
195+
use std::path::PathBuf;
196+
197+
let cwd = Path::new("/home/user");
198+
assert_eq!(
199+
make_absolute(Path::new("/absolute/path"), cwd),
200+
PathBuf::from("/absolute/path")
201+
);
202+
}
156203
}

src/main.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,18 @@ fn construct_config(mut opts: Opts, pattern_regexps: &[String]) -> Result<Config
245245
let command = extract_command(&mut opts, colored_output)?;
246246
let has_command = command.is_some();
247247

248+
let cwd = if opts.full_path {
249+
Some(env::current_dir().context(
250+
"Could not determine current directory. \
251+
This is required for --full-path.",
252+
)?)
253+
} else {
254+
None
255+
};
256+
248257
Ok(Config {
249258
case_sensitive,
250-
search_full_path: opts.full_path,
259+
cwd,
251260
ignore_hidden: !(opts.hidden || opts.rg_alias_ignore()),
252261
read_fdignore: !(opts.no_ignore || opts.rg_alias_ignore()),
253262
read_vcsignore: !(opts.no_ignore || opts.rg_alias_ignore() || opts.no_ignore_vcs),

src/walk.rs

Lines changed: 7 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ pub enum WorkerResult {
4242
// to box the Entry variant
4343
Entry(DirEntry),
4444
Error(ignore::Error),
45-
FatalError(ignore::Error),
4645
}
4746

4847
/// A batch of WorkerResults to send over a channel.
@@ -230,12 +229,6 @@ impl<'a, W: Write> ReceiverBuffer<'a, W> {
230229
print_error(err.to_string());
231230
}
232231
}
233-
WorkerResult::FatalError(err) => {
234-
if self.config.show_filesystem_errors {
235-
print_error(err.to_string());
236-
}
237-
return Err(ExitCode::GeneralError);
238-
}
239232
}
240233
}
241234

@@ -531,20 +524,7 @@ impl WorkerState {
531524
// Check the name first, since it doesn't require metadata
532525
let entry_path = entry.path();
533526

534-
let search_str = match search_str_for_entry(entry_path, config.search_full_path) {
535-
Ok(search_str) => search_str,
536-
Err(err) => {
537-
let error = ignore::Error::WithPath {
538-
path: entry_path.to_path_buf(),
539-
err: Box::new(ignore::Error::Io(err)),
540-
};
541-
542-
return match tx.send(WorkerResult::FatalError(error)) {
543-
Ok(_) => WalkState::Quit,
544-
Err(_) => WalkState::Quit,
545-
};
546-
}
547-
};
527+
let search_str = search_str_for_entry(entry_path, config.cwd.as_deref());
548528

549529
if !patterns
550530
.iter()
@@ -685,14 +665,14 @@ impl WorkerState {
685665

686666
fn search_str_for_entry<'a>(
687667
entry_path: &'a std::path::Path,
688-
search_full_path: bool,
689-
) -> io::Result<Cow<'a, OsStr>> {
690-
if search_full_path {
691-
let path_abs_buf = filesystem::path_absolute_form(entry_path)?;
692-
Ok(Cow::Owned(path_abs_buf.as_os_str().to_os_string()))
668+
cwd: Option<&std::path::Path>,
669+
) -> Cow<'a, OsStr> {
670+
if let Some(cwd) = cwd {
671+
let abs_path = filesystem::make_absolute(entry_path, cwd);
672+
Cow::Owned(abs_path.as_os_str().to_os_string())
693673
} else {
694674
match entry_path.file_name() {
695-
Some(filename) => Ok(Cow::Borrowed(filename)),
675+
Some(filename) => Cow::Borrowed(filename),
696676
None => unreachable!(
697677
"Encountered file system entry without a file name. This should only \
698678
happen for paths like 'foo/bar/..' or '/' which are not supposed to \
@@ -710,29 +690,3 @@ fn search_str_for_entry<'a>(
710690
pub fn scan(paths: &[PathBuf], patterns: Vec<Regex>, config: Config) -> Result<ExitCode> {
711691
WorkerState::new(patterns, config).scan(paths)
712692
}
713-
714-
#[cfg(test)]
715-
mod tests {
716-
use super::search_str_for_entry;
717-
use std::env;
718-
use std::fs;
719-
use std::path::Path;
720-
721-
#[test]
722-
#[cfg(all(not(windows), not(target_os = "illumos")))]
723-
fn full_path_search_returns_error_for_invalid_cwd() {
724-
let original_dir = env::current_dir().unwrap();
725-
let temp_dir = tempfile::tempdir().unwrap();
726-
let cwd = temp_dir.path().join("cwd");
727-
728-
fs::create_dir(&cwd).unwrap();
729-
env::set_current_dir(&cwd).unwrap();
730-
fs::remove_dir(&cwd).unwrap();
731-
732-
let result = search_str_for_entry(Path::new("relative/path"), true);
733-
734-
env::set_current_dir(original_dir).unwrap();
735-
736-
assert!(result.is_err());
737-
}
738-
}

0 commit comments

Comments
 (0)