Fix alias expansion failing when persistent flags precede the alias#12909
Fix alias expansion failing when persistent flags precede the alias#12909mango766 wants to merge 1 commit intogitbutlerapp:masterfrom
Conversation
`but --json st` would fail with "but st is not a command" because `expand_aliases()` always checked `args[1]` for the alias name. When root-level flags like `--json`, `-C <path>`, or `--status-after` appeared before the subcommand, `args[1]` was a flag, and the function bailed out early. Introduce `find_first_positional()` to skip leading flags (and their values) before locating the alias candidate. The expanded result preserves all surrounding arguments in their original positions. Closes gitbutlerapp#12697
|
Someone is attempting to deploy a commit to the GitButler Team on Vercel. A member of the Team first needs to authorize it. |
| } | ||
|
|
||
| /// Known root-level flags that consume the next argument as a value. | ||
| const FLAGS_WITH_VALUE: &[&str] = &["-C", "--current-dir", "-f", "--format"]; |
There was a problem hiding this comment.
This will probably go stale at some point, but then again, it should be rare to change global flags.
There was a problem hiding this comment.
Pull request overview
Fixes but CLI alias expansion when persistent/root-level flags (e.g. --json, -C <path>, --format <fmt>) appear before an alias, by scanning past leading flags to find the first positional argument to treat as the potential alias (reported in #12697).
Changes:
- Add
find_first_positional()to locate the first non-flag argument after leading persistent flags (and their values). - Update
expand_aliases()to expand aliases at the discovered positional index while preserving surrounding args. - Add unit tests covering alias expansion after boolean flags, flags-with-values, and all-flags passthrough.
You can also share your feedback on Copilot code review. Take the survey.
| // Find the first positional (non-flag) argument after "but", skipping | ||
| // leading flags and their values. This is necessary because persistent | ||
| // flags like `--json` or `-C <path>` may appear before the subcommand. | ||
| let alias_index = match find_first_positional(&args) { | ||
| Some(idx) => idx, | ||
| None => return Ok(args), // All args are flags, nothing to expand | ||
| }; |
| #[test] | ||
| fn expand_default_alias_after_boolean_flag() { | ||
| // `but --json st` should expand to `but --json status` | ||
| let args = vec![ | ||
| OsString::from("but"), | ||
| OsString::from("--json"), | ||
| OsString::from("st"), | ||
| ]; | ||
| let result = expand_aliases(args).unwrap(); | ||
| assert_eq!(result[0], OsString::from("but")); | ||
| assert_eq!(result[1], OsString::from("--json")); | ||
| assert_eq!(result[2], OsString::from("status")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn expand_default_alias_after_flag_with_value() { | ||
| // `but -C /some/path st` should expand to `but -C /some/path status` | ||
| let args = vec![ | ||
| OsString::from("but"), | ||
| OsString::from("-C"), | ||
| OsString::from("/some/path"), | ||
| OsString::from("stf"), | ||
| ]; | ||
| let result = expand_aliases(args).unwrap(); | ||
| assert_eq!(result[0], OsString::from("but")); | ||
| assert_eq!(result[1], OsString::from("-C")); | ||
| assert_eq!(result[2], OsString::from("/some/path")); | ||
| assert_eq!(result[3], OsString::from("status")); | ||
| assert_eq!(result[4], OsString::from("--files")); |
| fn expand_default_alias_after_flag_with_value() { | ||
| // `but -C /some/path st` should expand to `but -C /some/path status` | ||
| let args = vec![ | ||
| OsString::from("but"), | ||
| OsString::from("-C"), | ||
| OsString::from("/some/path"), | ||
| OsString::from("stf"), | ||
| ]; |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b78e47a626
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| /// Known root-level flags that consume the next argument as a value. | ||
| const FLAGS_WITH_VALUE: &[&str] = &["-C", "--current-dir", "-f", "--format"]; |
There was a problem hiding this comment.
Resolve aliases against the
-C repository
Adding -C/--current-dir to FLAGS_WITH_VALUE makes but -C repo <alias> enter expand_aliases(), but the lookup still uses gix::discover(".") a few lines below, so it reads but.alias.* from the caller's cwd instead of repo/.git/config. That means repo-local aliases created with but -C repo alias add ... still fail when invoked from outside that repository, which is exactly the flag combination this change is trying to support.
Useful? React with 👍 / 👎.
| if arg == "--" { | ||
| // Everything after `--` is positional; the next arg (if any) is the candidate. | ||
| return if i + 1 < args.len() { | ||
| Some(i + 1) |
There was a problem hiding this comment.
Stop expanding aliases after
--
Treating the first token after -- as an alias candidate changes the meaning of literal positionals. For example, but -- st (or but -C repo -- st) should pass the literal st through to the root positional parser, but this now rewrites it to status whenever the name matches a built-in or configured alias. Because -- is the explicit end-of-options marker, users lose the escape hatch for alias-like paths or selectors.
Useful? React with 👍 / 👎.
|
@mango766 Could you take a look at the bot-comments and at add your own comments to them (while addressing them or not)? |
Problem
Running
but --json st(or any command with root-level flags before an alias) fails with:This happens because
expand_aliases()always inspectsargs[1]as the potential alias. When persistent flags appear first,args[1]is a flag like--json, and the function short-circuits without ever seeing the actual alias.Reported in #12697.
Root Cause
In
crates/but/src/alias.rs,expand_aliases()hardcodes the alias position at index 1:Fix
Introduce
find_first_positional()which scans past leading flags and their values to locate the first positional argument. The function knows which root-level flags consume a value (-C,--current-dir,-f,--format) and skips them accordingly. Boolean flags (--json,-t,--status-after) are stepped over one at a time.The expanded result preserves all surrounding arguments in their original positions:
but --json st→but --json statusbut -C /path stf --verbose→but -C /path status --files --verboseTesting
Added unit tests covering:
but --json st)but -C /path stf)Closes #12697