Skip to content

Fix completion git wrapper - #5

Merged
chris-peterson merged 2 commits into
mainfrom
fix-completion-git-wrapper
Jul 28, 2026
Merged

Fix completion git wrapper#5
chris-peterson merged 2 commits into
mainfrom
fix-completion-git-wrapper

Conversation

@chris-peterson

@chris-peterson chris-peterson commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Closes #4

Context

git fi <TAB> offers filenames instead of branches and flags for a lot of people. git-fi ships completion, so this reads as broken rather than missing. The cause is that two different things named _git compete on the zsh function path and they dispatch subcommands differently: zsh's own _git calls _git-fi (hyphen), while the completion wrapper that ships with git — what Homebrew installs, and the common setup on macOS — calls _git_fi (underscore) under ksh emulation. git-fi ships a _git_fi, but it reads the command line from bash's COMP_WORDS, which git's zsh wrapper never sets, and it isn't on the fpath for zsh to autoload. Under that wrapper the completer generates nothing and zsh falls through to _files.

The fix reads the command line from git's portable $words/$cur so one _git_fi body serves both shells, and ships that body a second time as an fpath-autoloadable completions/_git_fi. Both come out of scripts/completion/git-fi.bash.tmpl, so they can't drift.

%%{ init: { 'look': 'handDrawn' } }%%
flowchart LR
    Tab["git fi TAB"] --> Which{"which _git is on fpath?"}
    Which -->|"zsh's own"| Dash["_git-fi"]
    Which -->|"git's wrapper"| Under["_git_fi"]
    Dash --> ZshFile["completions/_git-fi"]
    Under --> BashFile["completions/git-fi.bash in bash"]
    Under --> FpathFile["completions/_git_fi on the zsh fpath"]
    BashFile --> Shared["shared body from git-fi.bash.tmpl"]
    FpathFile --> Shared
Loading

A second, unrelated defect turned up while verifying the first, and is fixed here too: completions/_git-fi defines its completer but never calls it. zsh's autoload runs a completion file as the body of the function being autoloaded, so the first <TAB> in any shell executed the definitions and returned without completing — a silent beep, working only on the second tab. That one hits git-fi <TAB> and git fi <TAB> under zsh's built-in _git, and it is live on main today.

Review guide

Most to least critical.

  • git-fi.bash.tmpl:42 — the actual bug. $words in place of COMP_WORDS; everything else about the branch follows from this line. The helpers moved inside _git_fi on the same pass, so the file is one function definition and zsh's autoload runs the body.
  • git-fi.zsh.tmpl:62 — the second defect, one line. zsh's own _git ends the same way.
  • gen-docs.ts:72-93 — one template body, two outputs, differing only in the header and the leading #autoload tag. The new file joins the outputs list, so npm run verify:generated covers it.
  • cli.test.ts:97 — three assertions that pin the fixes: $words and not COMP_WORDS, the trailing _git-fi "$@", and byte-identical _git_fi bodies across the two generated files.
  • quickstart.md:48 — install docs. Names both providers, tells you how to recognise which one you have (filename fallback), and covers both files.
  • SPEC.md:66 — new CMP-02 for the two-provider requirement. The rest of the CMP block shifts up one to stay contiguous; STATUS.md follows.
  • scripts/trial.shnpm run trial:on / trial:off, for running the change against your real shell before publishing. Not in the package files.

completions/* and man/* are generated; read the templates and gen-docs.ts instead.

Validation

CI asserts on the contents of the generated files, which cannot tell you what a shell actually offers. So this was exercised against a real zsh 5.9 and git 2.55 driven through a pty, in a throwaway repo with feature-a (in fi), feature-b, feature-c on origin, reading the completion listing off the terminal.

All on the first <TAB>, both providers:

line git's wrapper zsh's built-in _git
git fi <TAB> 12 flags + help, install-completions help, install-completions
git fi -<TAB> 12 flags 12 flags, with descriptions
git fi --add <TAB> feature-b, feature-c same
git fi --remove <TAB> feature-a same
git-fi <TAB> help, install-completions same

--add excludes the branch already in fi and --remove offers only it, which is the action-aware filtering working through $words under both dispatchers. No filename fallback anywhere. The one asymmetry is the command position: git's wrapper lists the flags there, while zsh's _arguments holds them until you type a -. The quickstart says so.

`git fi` is dispatched by two providers: zsh's built-in `_git` calls
`_git-fi`, while git's own completion wrapper (the default on
macOS/Homebrew) calls `_git_fi` under ksh emulation. The shipped
`_git_fi` read the command line from bash's `COMP_WORDS`, which git's
zsh wrapper never sets, so `git fi <TAB>` fell back to filenames there.

Read the command line from git's portable `$words`/`$cur` so one
`_git_fi` works in both shells, and ship it as an fpath-autoloadable
`completions/_git_fi` (helpers nested inside the function so autoload
runs the body on the first tab). It is generated from the same source
as `git-fi.bash`, so the two can't drift.

Also stop dumping the branch list at the command position: `git fi
<TAB>` now offers the actions, options, and subcommands, and branch
names come once an action is chosen. A bare positional is only a
list-filter pattern, so the branch dump there was noise.

Add `npm run trial:on` / `trial:off` to run the change locally before
publishing.
`completions/_git-fi` defines `_git-fi` and its branch helpers and stops
there. zsh's autoload runs a completion file as the body of the function
it is autoloading, so the first `<TAB>` in a shell executed those
definitions and returned without completing anything — a silent beep.
The second tab, now calling the defined function, worked. Both zsh-side
entry points were affected: `git-fi <TAB>` and, under zsh's built-in
`_git`, `git fi <TAB>`.

End the file with `_git-fi "$@"`, the way zsh's own `_git` does, and
assert on the call in the completion tests so a regeneration can't drop
it.

Checked by driving a real zsh through a pty against a fixture repo with
branches, under both dispatch providers: git's own completion wrapper
and zsh's built-in `_git`. That run is also what corrected two claims in
the quickstart — at the command position the built-in `_git` offers the
subcommands and holds the flags until you type a `-`, and the fpath copy
of `_git_fi` now comes with the command that installs it rather than a
note pointing package maintainers at the file.
@chris-peterson
chris-peterson merged commit 51ec9c1 into main Jul 28, 2026
4 checks passed
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.

Add native git fi (two-word) zsh completion for git's own completion wrapper

1 participant