mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
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:
@ -1,3 +1,4 @@
|
||||
use nu_cmd_base::util::get_init_cwd;
|
||||
use nu_engine::command_prelude::*;
|
||||
use nu_utils::filesystem::{have_permission, PermissionResult};
|
||||
|
||||
@ -39,7 +40,10 @@ impl Command for Cd {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let physical = call.has_flag(engine_state, stack, "physical")?;
|
||||
let path_val: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
|
||||
let cwd = engine_state.cwd(Some(stack))?;
|
||||
|
||||
// If getting PWD failed, default to the initial directory. This way, the
|
||||
// user can use `cd` to recover PWD to a good state.
|
||||
let cwd = engine_state.cwd(Some(stack)).unwrap_or(get_init_cwd());
|
||||
|
||||
let path_val = {
|
||||
if let Some(path) = path_val {
|
||||
@ -52,13 +56,13 @@ impl Command for Cd {
|
||||
}
|
||||
};
|
||||
|
||||
let (path, span) = match path_val {
|
||||
let path = match path_val {
|
||||
Some(v) => {
|
||||
if v.item == "-" {
|
||||
if let Some(oldpwd) = stack.get_env_var(engine_state, "OLDPWD") {
|
||||
(oldpwd.to_path()?, v.span)
|
||||
oldpwd.to_path()?
|
||||
} else {
|
||||
(cwd, v.span)
|
||||
cwd
|
||||
}
|
||||
} else {
|
||||
// Trim whitespace from the end of path.
|
||||
@ -66,7 +70,7 @@ impl Command for Cd {
|
||||
&v.item.trim_end_matches(|x| matches!(x, '\x09'..='\x0d'));
|
||||
|
||||
// If `--physical` is specified, canonicalize the path; otherwise expand the path.
|
||||
let path = if physical {
|
||||
if physical {
|
||||
if let Ok(path) = nu_path::canonicalize_with(path_no_whitespace, &cwd) {
|
||||
if !path.is_dir() {
|
||||
return Err(ShellError::NotADirectory { span: v.span });
|
||||
@ -90,19 +94,12 @@ impl Command for Cd {
|
||||
return Err(ShellError::NotADirectory { span: v.span });
|
||||
};
|
||||
path
|
||||
};
|
||||
(path, v.span)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let path = nu_path::expand_tilde("~");
|
||||
(path, call.head)
|
||||
}
|
||||
None => nu_path::expand_tilde("~"),
|
||||
};
|
||||
|
||||
// Strip the trailing slash from the new path. This is required for PWD.
|
||||
let path = nu_path::strip_trailing_slash(&path);
|
||||
|
||||
// Set OLDPWD.
|
||||
// We're using `Stack::get_env_var()` instead of `EngineState::cwd()` to avoid a conversion roundtrip.
|
||||
if let Some(oldpwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
@ -113,7 +110,7 @@ impl Command for Cd {
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
PermissionResult::PermissionOk => {
|
||||
stack.add_env_var("PWD".into(), Value::string(path.to_string_lossy(), span));
|
||||
stack.set_cwd(path)?;
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
PermissionResult::PermissionDenied(reason) => Err(ShellError::IOError {
|
||||
|
Reference in New Issue
Block a user