Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/shell/snapshots/worktrunk__shell__tests__init_nu.snap
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ export def --env --wrapped wt [...args: string@"nu-complete wt"] {
($external | get 0.path)
}

# Strip surrounding quotes from `--flag="value"` / `-x='value'` tokens
# before forwarding. Nushell's `def --wrapped` re-tokenizes the
# space-separated form (`--flag "value"` → ["--flag", "value"]) but keeps
# the equals form as a single literal token (`--flag="value"` →
# ['--flag="value"']), so without this the binary receives the quote
# characters in argv and clap parses them as part of the value. See #2410.
let args = ($args | each {|a|
$a | str replace -r '^(-{1,2}[^=]+)=(["''])(.*)["'']$' '${1}=${3}'
})

# `list` is the only command that benefits from streaming stdout (progressive
# table rendering). It never emits directives, so we skip directive processing
# and let the binary be the last expression — stdout flows through pipes.
Expand Down
10 changes: 10 additions & 0 deletions templates/nushell.nu
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ export def --env --wrapped {{ cmd }} [...args: string@"nu-complete {{ cmd }}"] {
($external | get 0.path)
}

# Strip surrounding quotes from `--flag="value"` / `-x='value'` tokens
# before forwarding. Nushell's `def --wrapped` re-tokenizes the
# space-separated form (`--flag "value"` → ["--flag", "value"]) but keeps
# the equals form as a single literal token (`--flag="value"` →
# ['--flag="value"']), so without this the binary receives the quote
# characters in argv and clap parses them as part of the value. See #2410.
let args = ($args | each {|a|
$a | str replace -r '^(-{1,2}[^=]+)=(["''])(.*)["'']$' '${1}=${3}'
})

# `list` is the only command that benefits from streaming stdout (progressive
# table rendering). It never emits directives, so we skip directive processing
# and let the binary be the last expression — stdout flows through pipes.
Expand Down
40 changes: 40 additions & 0 deletions tests/integration_tests/shell_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,46 @@ mod unix_tests {
});
}

/// Regression test for #2410.
///
/// Nushell's `def --wrapped wt [...args]` re-tokenizes the space-separated
/// form (`--base "value"`) into two tokens but keeps the equals form
/// (`--base="value"`) as a single literal token, including the surrounding
/// quote characters. Without quote stripping in the wrapper, the worktrunk
/// binary sees `--base="releases/4.x.x"` in argv and clap parses the value
/// as the literal string `"releases/4.x.x"` (with quotes), so branch
/// resolution fails.
///
/// nu-only — bash/zsh/fish strip quotes in their own tokenizer before the
/// wrapper function ever runs.
#[test]
fn test_wrapper_nu_strips_quoted_equals_flag() {
let repo = TestRepo::new();
repo.commit("Initial commit");
repo.create_branch("releases/4.x.x");

let output = exec_through_wrapper(
"nu",
&repo,
"switch",
&["--create", "feature", "--base=\"releases/4.x.x\""],
);

assert_eq!(
output.exit_code, 0,
"Quote-stripping should let `--base=\"...\"` succeed.\nOutput:\n{}",
output.combined
);
output.assert_no_directive_leaks();
// ANSI codes wrap branch names in bold, so check for the surrounding text.
assert!(
output.combined.contains("Created branch")
&& output.combined.contains("releases/4.x.x"),
"Should show success message referencing both branches.\nOutput:\n{}",
output.combined
);
}

#[rstest]
#[case("bash")]
#[case("zsh")]
Expand Down
Loading