One model, every command
List candidates → filter and pick in the fuzzy finder with a preview → run the git operation. Learn it once and every subcommand behaves the same way.
Pick, search, and trace your way through git.
You should not have to remember a branch name or a commit hash to
get work done. Every fuzgit subcommand follows the same model:
build the candidate list, filter and pick it in a fuzzy finder
(with a preview), then run the git operation. The fuzzy finder is
skim, embedded as a
library — no external fzf or sk binary is
required.
package fuzgit · command gz · written in Rust · MIT
Picking
The two commands where a fuzzy finder pays off immediately:
gz branch and gz stash. Type the fragment
you do remember, let the preview confirm it is the right one, and
press Enter to run the git operation.
$ gz branch --all
> * main
feature/login
origin/feature/search
* as git branch. The
preview opens with a summary of the highlighted branch —
↑ ahead / ↓ behind its upstream,
the upstream itself, and the last commit — and the 50 most
recent commits follow (git log --oneline --decorate).
With --all, remote-tracking branches join the list
— picking origin/feature/search switches by the
short name, so git’s DWIM creates the tracking local branch.
$ gz stash apply
> stash@{0}: On main: half-done auth refactor
stash@{1}: On feature/search: draft of the search form
apply / pop /
drop are stash@{n}: <message>, so
you filter by what you wrote, not by index. The preview
is git stash show -p --color=always, and
drop — the one step that cannot be undone — asks
for confirmation ([y/N]) first.
$ gz stash push -m "half-done auth refactor"
>> M src/auth.rs
> M src/session.rs
M README.md
push picks files rather than a stash.
Tab selects several of them and only the files you
picked are stashed — everything you left alone stays in the
working tree. Staged changes are stashed too, so the preview is
the diff against HEAD (git diff HEAD);
-u adds untracked files to the list.
In one go
The same model, except that what you pick is a set:
gz fetch -s updates several repositories and
gz pull brings several branches up to their upstream —
one selection, one run, one summary.
$ gz fetch --siblings
The current repository is preselected. Tab: toggle the selection / Enter: fetch | 1 excluded (no remote / bare)
>> mike origin/main
alpha origin/main
zulu origin/main
-s (--siblings)
scans the parent directory of the current worktree root
one level deep only and offers every directory that
contains a .git; a line reads
<directory> <remote>/<current branch>.
Repositories that cannot be fetched are excluded, never silently
— the number skipped is in the header. The ones you pick are
fetched in parallel, four at a time
(git config fuzgit.fetchJobs <n> changes that,
and 1 makes it fully serial again).
$ gz pull
The current branch is preselected. Tab: toggle the selection / Enter: integrate with a fast-forward only | 2 excluded (no upstream / remote not registered / in use by another worktree)
>> * main → origin/main
feature-a → origin/feature-a
$ gz pull
[1/4] main
[2/4] alpha
[3/4] diverged
[4/4] zeta
3 succeeded / 1 failed (failed: diverged)
A branch that could not fast-forward can be integrated with `gz pull --rebase` or `gz pull --merge` after switching to it
[<n>/<total>]
so you can tell whose output git is printing.
A failure never stops the run, and the tally comes at the
end; gz fetch --siblings reports the same way.
fuzgit only points at the next step — it never starts
gz pull --rebase for you.
Both of these runs can tell you when they are done, which helps when you have stepped away from the terminal:
git config --global fuzgit.notify true
It is off unless you turn it on, and it stays quiet for runs
shorter than ten seconds. The body is the count only — no
repository, branch or path names. Whether a banner actually appears
depends on the machine (macOS uses osascript, Linux
uses notify-send), and fuzgit never treats a missing
notifier as a failure: the tally always goes to stderr, so the
notification is a convenience and never the only way you learn the
result.
Note: the terminal excerpts above show fuzgit’s
English output, which is the default; the same lines come out in
Japanese with --lang ja.
Everything git prints (update tables, conflicts, authentication
prompts) comes straight from git.
Why fuzgit
List candidates → filter and pick in the fuzzy finder with a preview → run the git operation. Learn it once and every subcommand behaves the same way.
A preview is regenerated on every cursor move, so a network
round trip there would block the whole display. Even
gz pr, which lists GitHub pull requests, fetches
their bodies once up front so the preview stays a local draw.
gz fetch, gz pull and
gz pr use the network. Everything else is local —
pushing is left to plain git push.
Destructive operations confirm first, gz pull is
fast-forward only, merge shows a conflict prediction, and
pushing is left to git. History is rewritten only when you spell
it out — gz pull --rebase and nothing else.
git is always executed with an argument array, never through a shell, and previews run git from Rust instead of handing skim a command string. Picked values are verified against the candidate list before they reach git.
Candidates are read with
gitoxide (gix).
Anything that would need one git process per
candidate — ahead/behind across repositories, for instance — is
moved into the preview so the list appears immediately.
Language
Messages, confirmation prompts, finder headers and
--help come in English and Japanese. English is the
default, and a single git config entry makes it Japanese
everywhere.
git config --global fuzgit.lang ja # persistent (--local sets it per repository)
gz --lang ja branch # one-off, on any subcommand
| Priority | Source |
|---|---|
| 1 |
--lang <ja|en|auto> — a global option,
accepted by every subcommand
|
| 2 | FUZGIT_LANG environment variable |
| 3 |
git config fuzgit.lang — system / global /
local / worktree all apply as usual
|
| 4 |
LC_ALL → LC_MESSAGES →
LANGUAGE → LANG
|
| 5 | fallback: en |
Layers 1–3 are explicit instructions to fuzgit, so a value
other than ja / en / auto
stops with an error. Layer 4 only describes the environment, so a
value fuzgit cannot interpret — C and
POSIX included — is not an error and resolution moves
on. auto skips the remaining explicit layers and reads
the environment. There is no fuzgit configuration file: it borrows
git’s fuzgit.lang key, which is read without
starting a git process and works outside a repository
too.
Two limits. fuzgit tells the git commands it runs
which language to speak, but whether git has that translation
is not guaranteed — it depends on the git build (NLS) and the
installed locale data, and git upstream ships no Japanese
catalog, so git’s own output stays English even with
ja. And the text clap prints on its own
(Usage:, Options:,
Commands:, parser errors) stays English,
because clap 4 has no localization hook; fuzgit’s own
descriptions in --help do switch.
Reference
gz <subcommand> [options]. Running
gz with no arguments — or gz --help —
lists the subcommands.
| Subcommand | What it does | Selection |
|---|---|---|
gz branch |
Pick a branch and switch to it (subcommands also create, delete and tidy up branches) | single / depends on subcommand |
gz log |
Trace the commit history and print the full hash to stdout (--action picks what to do next) |
single |
gz cherry-pick |
Pick commits and cherry-pick them | multiple |
gz restore |
Pick files to restore or unstage | multiple |
gz add |
Pick unstaged and untracked files and stage them | multiple |
gz stash <subcommand> |
Stash changes away, and search stashes to apply or drop them | depends on subcommand |
gz reflog |
Trace the HEAD reflog and recover commits you thought were lost (--action picks what to do next) |
single |
gz commit |
Pick changed files and commit only those | multiple |
gz fixup |
Pick the commit to amend and create a fixup commit | single |
gz merge |
Pick the branch to merge (recovery menu while one is in progress) | single |
gz rebase |
Pick the base to rebase onto (recovery menu while one is in progress) | single |
gz revert |
Pick the commits to undo and revert them | multiple |
gz status |
List changed files and act on the ones you pick (two-step selection) | multiple → single |
gz diff |
Pick what to compare and show the diff | depends on mode |
gz fetch |
Decide what to fetch and fetch it (--siblings also fetches the repositories next door, in parallel; uses the network) |
single / multiple with --siblings |
gz pull |
Pick branches and bring them up to their upstream in one go (fast-forward only; --rebase / --merge integrate the current branch instead; uses the network) |
multiple / none with --rebase |
gz worktree |
List and manage worktrees (add <name> creates one next to the repository) |
single |
gz pr |
Pick a GitHub pull request and check it out (--worktree <name> opens it in a review worktree; needs the gh CLI, uses the network) |
single |
Install
The formula and the package are called fuzgit; whichever
route you take, the installed command is gz.
brew install hatohato25/fuzgit/fuzgit
gz --version
Prebuilt binaries are published for macOS (Apple Silicon and Intel) and Linux (x86_64).
curl -fsSL https://raw.githubusercontent.com/hatohato25/fuzgit/main/install.sh | sh
It downloads the release tarball for your machine,
verifies its SHA-256 checksum, and puts gz in
~/.local/bin — no sudo, and nothing
written outside that directory. If that directory is not on your
PATH the script prints the line to add;
it does not edit your shell profile for you.
--version <TAG> and
--bin-dir <DIR> (or FUZGIT_VERSION /
FUZGIT_BIN_DIR) change what and where. Only x86_64 is
published for Linux today; on aarch64 the script stops
and points at the source build.
fuzgit is not on crates.io yet, so install it from a local clone.
git clone https://github.com/hatohato25/fuzgit.git
cd fuzgit
cargo install --path .
This installs ~/.cargo/bin/gz. To try it without
installing:
cargo build --release
./target/release/gz --help
git; gix is used to read repository data.