Fix completion git wrapper - #5
Merged
Merged
Conversation
`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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_gitcompete on the zsh function path and they dispatch subcommands differently: zsh's own_gitcalls_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'sCOMP_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/$curso one_git_fibody serves both shells, and ships that body a second time as an fpath-autoloadablecompletions/_git_fi. Both come out ofscripts/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 --> SharedA second, unrelated defect turned up while verifying the first, and is fixed here too:
completions/_git-fidefines 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 hitsgit-fi <TAB>andgit fi <TAB>under zsh's built-in_git, and it is live onmaintoday.Review guide
Most to least critical.
git-fi.bash.tmpl:42— the actual bug.$wordsin place ofCOMP_WORDS; everything else about the branch follows from this line. The helpers moved inside_git_fion 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_gitends the same way.gen-docs.ts:72-93— one template body, two outputs, differing only in the header and the leading#autoloadtag. The new file joins the outputs list, sonpm run verify:generatedcovers it.cli.test.ts:97— three assertions that pin the fixes:$wordsand notCOMP_WORDS, the trailing_git-fi "$@", and byte-identical_git_fibodies 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— newCMP-02for the two-provider requirement. The rest of the CMP block shifts up one to stay contiguous;STATUS.mdfollows.scripts/trial.sh—npm run trial:on/trial:off, for running the change against your real shell before publishing. Not in the packagefiles.completions/*andman/*are generated; read the templates andgen-docs.tsinstead.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-con origin, reading the completion listing off the terminal.All on the first
<TAB>, both providers:_gitgit fi <TAB>help,install-completionshelp,install-completionsgit fi -<TAB>git fi --add <TAB>feature-b,feature-cgit fi --remove <TAB>feature-agit-fi <TAB>help,install-completions--addexcludes the branch already in fi and--removeoffers only it, which is the action-aware filtering working through$wordsunder both dispatchers. No filename fallback anywhere. The one asymmetry is the command position: git's wrapper lists the flags there, while zsh's_argumentsholds them until you type a-. The quickstart says so.