Try to make hide-env respects overlays (#15904)

# Description
Closes: #15755
I think it's a good feature, to achieve this, we need to get all hidden
envs(it's defined in `get_hidden_env_vars`, and then restore these envs
back to stack)

# User-Facing Changes
### Before
```nushell
> $env.foo = 'bar'
> overlay new xxx
> hide-env foo
> overlay hide xxx
> $env.foo
Error: nu:🐚:column_not_found

  × Cannot find column 'foo'
   ╭─[entry #21:5:1]
 4 │ overlay hide xxx
 5 │ $env.foo
   · ────┬───┬
   ·     │   ╰── value originates here
   ·     ╰── cannot find column 'foo'
   ╰────
```

### After
```nushell
> $env.foo = 'bar'
> overlay new xxx
> hide-env foo
> overlay hide xxx
> $env.foo
bar
```

## Note
But it doesn't work if it runs the example code in script:
`nu -c "$env.foo = 'bar'; overlay new xxx; hide-env foo; overlay hide
xxx; $env.foo"`
still raises an error says `foo` doesn't found. That's because if we run
the script at once, the envs in stack doesn't have a chance to merge
back into `engine_state`, which is only called in `repl`.

It introduces some sort of inconsistency, but I think users use overlays
mostly in repl, so it's good to have such feature first.

# Tests + Formatting
Added 2 tests

# After Submitting
NaN
This commit is contained in:
Wind 2025-06-13 07:22:23 +08:00 committed by GitHub
parent 2fe25d6299
commit 81e86c40e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 66 additions and 1 deletions

View File

@ -86,12 +86,16 @@ impl Command for OverlayHide {
vec![]
};
// also restore env vars which has been hidden
let env_vars_to_restore = stack.get_hidden_env_vars(&overlay_name.item, engine_state);
stack.remove_overlay(&overlay_name.item);
for (name, val) in env_vars_to_restore {
stack.add_env_var(name, val);
}
for (name, val) in env_vars_to_keep {
stack.add_env_var(name, val);
}
Ok(PipelineData::empty())
}

View File

@ -414,6 +414,37 @@ impl Stack {
result
}
/// Get hidden envs, but without envs defined previously in `excluded_overlay_name`.
pub fn get_hidden_env_vars(
&self,
excluded_overlay_name: &str,
engine_state: &EngineState,
) -> HashMap<String, Value> {
let mut result = HashMap::new();
for overlay_name in self.active_overlays.iter().rev() {
if overlay_name == excluded_overlay_name {
continue;
}
if let Some(env_names) = self.env_hidden.get(overlay_name) {
for n in env_names {
if result.contains_key(n) {
continue;
}
// get env value.
if let Some(Some(v)) = engine_state
.env_vars
.get(overlay_name)
.map(|env_vars| env_vars.get(n))
{
result.insert(n.to_string(), v.clone());
}
}
}
}
result
}
/// Same as get_env_vars, but returns only the names as a HashSet
pub fn get_env_var_names(&self, engine_state: &EngineState) -> HashSet<String> {
let mut result = HashSet::new();

View File

@ -856,6 +856,36 @@ fn overlay_hide_renamed_overlay() {
assert!(actual_repl.err.contains("external_command"));
}
#[test]
fn overlay_hide_restore_hidden_env() {
let inp = &[
"$env.foo = 'bar'",
"overlay new aa",
"hide-env foo",
"overlay hide aa",
"$env.foo",
];
let actual_repl = nu!(nu_repl_code(inp));
assert_eq!(actual_repl.out, "bar");
}
#[test]
fn overlay_hide_dont_restore_hidden_env_which_is_introduce_currently() {
let inp = &[
"overlay new aa",
"$env.foo = 'bar'",
"hide-env foo", // hide the env in overlay `aa`
"overlay hide aa",
"'foo' in $env",
];
let actual_repl = nu!(nu_repl_code(inp));
assert_eq!(actual_repl.out, "false");
}
#[test]
fn overlay_hide_and_add_renamed_overlay() {
let inp = &[