Implement PWD recovery (#12779)

This PR has two parts. The first part is the addition of the
`Stack::set_pwd()` API. It strips trailing slashes from paths for
convenience, but will reject otherwise bad paths, leaving PWD in a good
state. This should reduce the impact of faulty code incorrectly trying
to set PWD.
(https://github.com/nushell/nushell/pull/12760#issuecomment-2095393012)

The second part is implementing a PWD recovery mechanism. PWD can become
bad even when we did nothing wrong. For example, Unix allows you to
remove any directory when another process might still be using it, which
means PWD can just "disappear" under our nose. This PR makes it possible
to use `cd` to reset PWD into a good state. Here's a demonstration:

```sh
mkdir /tmp/foo
cd /tmp/foo

# delete "/tmp/foo" in a subshell, because Nushell is smart and refuse to delete PWD
nu -c 'cd /; rm -r /tmp/foo'

ls          # Error:   × $env.PWD points to a non-existent directory
            # help: Use `cd` to reset $env.PWD into a good state

cd /
pwd         # prints /
```

Also, auto-cd should be working again.
This commit is contained in:
YizhePKU
2024-05-11 00:06:33 +08:00
committed by GitHub
parent 70c01bbb26
commit b9a7faad5a
9 changed files with 214 additions and 33 deletions

View File

@ -931,13 +931,12 @@ impl EngineState {
/// directory on the stack that have yet to be merged into the engine state.
pub fn cwd(&self, stack: Option<&Stack>) -> Result<PathBuf, ShellError> {
// Helper function to create a simple generic error.
// Its messages are not especially helpful, but these errors don't occur often, so it's probably fine.
fn error(msg: &str) -> Result<PathBuf, ShellError> {
fn error(msg: &str, cwd: impl AsRef<Path>) -> Result<PathBuf, ShellError> {
Err(ShellError::GenericError {
error: msg.into(),
msg: "".into(),
msg: format!("$env.PWD = {}", cwd.as_ref().display()),
span: None,
help: None,
help: Some("Use `cd` to reset $env.PWD into a good state".into()),
inner: vec![],
})
}
@ -967,21 +966,21 @@ impl EngineState {
// Technically, a root path counts as "having trailing slashes", but
// for the purpose of PWD, a root path is acceptable.
if !is_root(&path) && has_trailing_slash(&path) {
error("$env.PWD contains trailing slashes")
error("$env.PWD contains trailing slashes", path)
} else if !path.is_absolute() {
error("$env.PWD is not an absolute path")
error("$env.PWD is not an absolute path", path)
} else if !path.exists() {
error("$env.PWD points to a non-existent directory")
error("$env.PWD points to a non-existent directory", path)
} else if !path.is_dir() {
error("$env.PWD points to a non-directory")
error("$env.PWD points to a non-directory", path)
} else {
Ok(path)
}
} else {
error("$env.PWD is not a string")
error("$env.PWD is not a string", format!("{pwd:?}"))
}
} else {
error("$env.PWD not found")
error("$env.PWD not found", "")
}
}

View File

@ -592,6 +592,40 @@ impl Stack {
self.out_dest.pipe_stderr = None;
self
}
/// Set the PWD environment variable to `path`.
///
/// This method accepts `path` with trailing slashes, but they're removed
/// before writing the value into PWD.
pub fn set_cwd(&mut self, path: impl AsRef<std::path::Path>) -> Result<(), ShellError> {
// Helper function to create a simple generic error.
// Its messages are not especially helpful, but these errors don't occur often, so it's probably fine.
fn error(msg: &str) -> Result<(), ShellError> {
Err(ShellError::GenericError {
error: msg.into(),
msg: "".into(),
span: None,
help: None,
inner: vec![],
})
}
let path = path.as_ref();
if !path.is_absolute() {
error("Cannot set $env.PWD to a non-absolute path")
} else if !path.exists() {
error("Cannot set $env.PWD to a non-existent directory")
} else if !path.is_dir() {
error("Cannot set $env.PWD to a non-directory")
} else {
// Strip trailing slashes, if any.
let path = nu_path::strip_trailing_slash(path);
let value = Value::string(path.to_string_lossy(), Span::unknown());
self.add_env_var("PWD".into(), value);
Ok(())
}
}
}
#[cfg(test)]