Skip to content

Fix alias expansion failing when persistent flags precede the alias#12909

Draft
mango766 wants to merge 1 commit intogitbutlerapp:masterfrom
mango766:fix/alias-expansion-with-leading-flags
Draft

Fix alias expansion failing when persistent flags precede the alias#12909
mango766 wants to merge 1 commit intogitbutlerapp:masterfrom
mango766:fix/alias-expansion-with-leading-flags

Conversation

@mango766
Copy link

Problem

Running but --json st (or any command with root-level flags before an alias) fails with:

Error: "but st" is not a command. Type "but --help" to see all available commands.

This happens because expand_aliases() always inspects args[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:

let potential_alias = match args[1].to_str() { ... };
if potential_alias.starts_with('-') || is_known_subcommand(potential_alias) {
    return Ok(args);  // <-- bails here when args[1] is "--json"
}

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 stbut --json status
  • but -C /path stf --verbosebut -C /path status --files --verbose

Testing

Added unit tests covering:

  • Alias after boolean flag (but --json st)
  • Alias after flag with value (but -C /path stf)
  • Trailing args preserved after expansion
  • All-flags-no-positional passthrough

Closes #12697

`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
@vercel
Copy link

vercel bot commented Mar 18, 2026

Someone is attempting to deploy a commit to the GitButler Team on Vercel.

A member of the Team first needs to authorize it.

@Byron Byron requested a review from Copilot March 18, 2026 22:54
Copy link
Collaborator

@Byron Byron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for contributing!

I think this looks good and solves a real problem, so I'd definitely want to merge this.

@codex review

}

/// Known root-level flags that consume the next argument as a value.
const FLAGS_WITH_VALUE: &[&str] = &["-C", "--current-dir", "-f", "--format"];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will probably go stale at some point, but then again, it should be rare to change global flags.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +57 to +63
// 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
};
Comment on lines +254 to +282
#[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"));
Comment on lines +269 to +276
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"),
];
Copy link
Contributor

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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"];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +120 to +123
if arg == "--" {
// Everything after `--` is positional; the next arg (if any) is the candidate.
return if i + 1 < args.len() {
Some(i + 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge 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 👍 / 👎.

@Byron
Copy link
Collaborator

Byron commented Mar 18, 2026

@mango766 Could you take a look at the bot-comments and at add your own comments to them (while addressing them or not)?
When done, I can swoop in and sign your commit to make the merge, if you didn't do it already by then.
Setting to draft while the review comments are addressed.

@Byron Byron marked this pull request as draft March 18, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Persistent flags do not work on root level for aliases

3 participants