fuzgit

fuzgit documentationfuzgit ドキュメント

fuzgit is a git CLI built around picking, searching and tracing with a fuzzy finder. The goal is to let you finish everyday git work without remembering branch names or commit hashes exactly: you narrow the list down and pick.

Every subcommand follows the same model — build the candidate list → filter and pick it in the fuzzy finder (with a preview) → run the git operation. The fuzzy finder is skim embedded as a library, so no external fzf or sk binary is needed.

  • Package name: fuzgit
  • Command (binary) name: gz

fuzgit speaks English and Japanese, English being the default (see Language). Console excerpts on this page show the default English display.

fuzzy finder で「選ぶ」「探す」「辿る」ことを軸にした git 操作 CLI ツールです。

ブランチ名やコミットハッシュを正確に覚えていなくても、絞り込みと選択だけで日常的な git 操作を完結できることを目指しています。すべてのサブコマンドが 候補一覧の取得 → fuzzy finder で絞り込み・選択(+プレビュー)→ git 操作の実行という同じ操作モデルに従います。fuzzy finder は skim をライブラリとして組み込んでいるため、 外部の fzf / sk バイナリは不要です。

  • パッケージ名: fuzgit
  • 実行コマンド名(バイナリ名): gz

Requirements前提条件 #

  • git on the system (required). fuzgit reads the repository with gix, but every write (switch, cherry-pick, restore, add, stash…) and every coloured diff preview runs the system git. Without it on PATH, fuzgit stops with “git was not found. Install git and make sure it is on your PATH”.
  • Git 2.38 or newer — only for the conflict prediction of gz merge. The prediction needs git merge-tree --write-tree. On older git the prediction is skipped and the merge still runs; the confirmation prompt says so.
  • Git 2.32 or newer — only for the gz stash preview. It asks git stash show for --include-untracked. Older git shows an error in the preview pane; picking and applying a stash still works. Anything that satisfies the 2.38 above satisfies this too.
  • A stable Rust toolchain — only to build from source. Rust 1.85 or newer (the crate uses edition 2024); development is verified with 1.95.0.
  • システムの git(必須)
    リポジトリの読み取りには gix を使いますが、書き込み(switch・cherry-pick・ restore・add・stash など)と プレビュー用の色付き差分は、いずれもシステムの git を 実行します。PATH 上に無い場合は「git コマンドが見つかりません」と 表示して停止します。
  • Git 2.38 以降 — gz merge のコンフリクト予測にのみ必要
    予測には git merge-tree --write-tree を使います。それより古い git では予測だけを省略し、merge はそのまま実行します (省略した旨は確認プロンプトに表示されます)。
  • Git 2.32 以降 — gz stash のプレビューにのみ必要
    git stash show に --include-untracked を渡すためです。 これより古い git ではプレビューにエラーが出ますが、stash の選択と適用は動きます。 上の 2.38 を満たしていれば、こちらも満たされます。
  • stable の Rust ツールチェイン — ソースからビルドする場合のみ
    Rust 1.85 以降(edition 2024 のため)。開発時の確認は 1.95.0 です。

Installationインストール #

macOS — Homebrew

brew install hatohato25/fuzgit/fuzgit

Linux / WSL — install script

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 outside that directory. If that directory is not on PATH, the script prints the line to add — it does not edit your shell profile for you.

OptionEnvironment variableDescription
--version <TAG>FUZGIT_VERSIONRelease to install (defaults to the latest)
--bin-dir <DIR>FUZGIT_BIN_DIRWhere to put gz (defaults to ~/.local/bin)
curl -fsSL .../install.sh | sh -s -- --bin-dir /usr/local/bin

Only x86_64 is published for Linux today. On aarch64 the script stops and points at the source build rather than installing a binary that cannot run.

Heads up

Piping a script into a shell runs code you have not read. The script is install.sh at the root of the repository — read it first if you would rather, then run it locally.

From source

fuzgit is not published to crates.io, so clone the repository and install from the local checkout.

git clone https://github.com/hatohato25/fuzgit.git
cd fuzgit
cargo install --path .

This installs ~/.cargo/bin/gz (the package is fuzgit, the command is gz).

To try it without installing:

cargo build --release
./target/release/gz --help

macOS — Homebrew

brew install hatohato25/fuzgit/fuzgit

Linux / WSL — インストールスクリプト

curl -fsSL https://raw.githubusercontent.com/hatohato25/fuzgit/main/install.sh | sh

実行環境に合うリリース資産を取得し、SHA-256 チェックサムを検証した うえで gz を ~/.local/bin へ配置します。 sudo は不要で、そのディレクトリの外には何も書きません。 PATH に無い場合は追加すべき行を表示するだけで、 シェルの設定ファイルは書き換えません。

オプション環境変数説明
--version <TAG>FUZGIT_VERSION導入するリリース(既定は最新)
--bin-dir <DIR>FUZGIT_BIN_DIRgz の配置先(既定は ~/.local/bin)
curl -fsSL .../install.sh | sh -s -- --bin-dir /usr/local/bin

現在 Linux 向けに配布しているのは x86_64 のみです。 aarch64 では動かないバイナリを入れずに停止し、ソースからの ビルドを案内します。

注意

スクリプトをシェルへパイプするのは、読んでいないコードを実行することでも あります。中身はリポジトリ直下の install.sh です。先に読んでから 手元で実行しても構いません。

ソースからインストール

crates.io へは未公開のため、リポジトリを取得してローカルからインストールします。

git clone https://github.com/hatohato25/fuzgit.git
cd fuzgit
cargo install --path .

~/.cargo/bin/gz がインストールされます(パッケージ名は fuzgit、コマンド名は gz)。

インストールせずに試す場合:

cargo build --release
./target/release/gz --help

Usage使い方 #

gz <subcommand> [options]

Running gz with no arguments, or gz --help, lists the subcommands.

引数なしの gz および gz --help で サブコマンド一覧を表示します。

An option gets a short form only when git itself spells it the same way — gz restore -S (git restore -S), gz fetch -p (git fetch -p), gz pull -r (git pull -r), gz branch delete -f (git branch -f) and so on. fuzgit-specific options are long-only, and a letter git already uses for something else is never reused: gz worktree add takes no short form because git has no short spelling for it either.

オプションの短縮形は、git 本体に同じ意味の綴りがあるものだけに 付けています(gz restore -S = git restore -S、gz fetch -p = git fetch -p、gz pull -r = git pull -r、gz branch delete -f = git branch -f など)。fuzgit 固有のオプションは長い綴りだけとし、 git が別の意味で使っている文字は流用しません。gz worktree add に 短縮形が無いのは、git 側にも対応する綴りが無いからです。

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) コミット履歴を辿り、フルハッシュを標準出力へ出す(--action で次の操作を選ぶ) single単一
gz cherry-pick Pick commits and cherry-pick them コミットを選んで cherry-pick する 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 変更を stash へ退避し、stash を検索して適用・破棄する depends on subcommand サブコマンド次第
gz reflog Trace the HEAD reflog and recover lost commits (--action picks what to do next) HEAD の reflog を辿り、失われたコミットを取り出す(--action で次の操作を選ぶ) single単一
gz commit Pick changed files and commit only those 変更ファイルを選んで、選んだものだけをコミットする multiple複数
gz fixup Pick the commit to amend and create a fixup commit 修正対象のコミットを選んで fixup コミットを作る single単一
gz merge Pick the branch to merge (recovery menu while one is in progress) merge するブランチを選ぶ(進行中は復帰メニュー) single単一
gz rebase Pick the base to rebase onto (recovery menu while one is in progress) rebase の base を選ぶ(進行中は復帰メニュー) single単一
gz revert Pick the commits to undo and revert them 打ち消すコミットを選んで revert する multiple複数
gz status List changed files and act on the ones you pick (two-step selection) 変更ファイルを一覧し、選んだファイルに操作を行う(2 段選択) 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) fetch の対象を決めて取得する(--siblings で隣のリポジトリも並列に一括取得。ネットワークを使う) single / multiple with --siblings 単一/--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) ブランチを選んで upstream へまとめて追随させる(fast-forward のみ。--rebase / --merge は現在のブランチが対象。ネットワークを使う) multiple / none with --rebase複数/--rebase は選択なし
gz worktree List and manage worktrees (add <name> creates one next to the repository) worktree を一覧・管理する(add <name> はリポジトリの隣に作る) 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) GitHub の PR を選んで checkout する(--worktree <name> でレビュー用 worktree に開く。gh CLI が必要・ネットワークを使う) single単一

Each command heading below carries chips for what the section contains. They describe the section as a whole — an option or a subcommand inside it, not necessarily the bare command.

  • network talks to a remote.
  • y/N asks before something you cannot take back.
  • multi-select Tab picks more than one candidate.

以下の各コマンド見出しには、その節に何が含まれるかを示すチップを付けています。 対象は節全体であり、素のコマンドではなく中のオプション・サブコマンドを 指している場合もあります。

  • 通信 リモートと通信します。
  • y/N 取り消せない操作の前に確認します。
  • 複数選択 Tab で複数の候補を選べます。

Status codes in file lists ファイル一覧の状態コード #

Every command that lists files — gz restore, gz add, gz commit, gz status — prefixes each line with the same two status characters as git status --short, in the same colours: green for the index (staged) column, red for the working-tree column, and red for both characters of an untracked file (??).

One thing differs: the two characters are left-aligned. A change that exists only in the working tree reads M  where git writes  M. The column stays two characters wide, so the paths still line up.

Where colour is the only signal

Because of that alignment, M  means either a staged change or an unstaged one, and only the colour tells them apart. Without colour (NO_COLOR, or output through a pipe) the two look identical.

ファイルを一覧するコマンド(gz restore・ gz add・gz commit・ gz status)は、行頭に git status --short と同じ 2 文字の状態コードを、同じ配色で表示します — index(staged)列は緑、作業ツリー列は赤、未追跡(??)は 2 文字とも赤です。

1 点だけ git と異なり、2 文字を左詰めで表示します。 作業ツリーだけの変更は、git の  M ではなく M  になります。列幅は 2 文字のままなので、パスの開始位置は 揃います。

色だけが頼りになる場面

左詰めにしたため M  は staged と unstaged のどちらでもあり得ます。 両者を区別するのは色だけです。NO_COLOR や パイプ経由など色の付かない環境では、同じ見た目になります。

Language表示言語 #

Every piece of text fuzgit produces — messages, errors, finder headers, confirmation prompts and --help — is available in English and Japanese. The language is detected from your locale, and falls back to English whenever the locale is not Japanese or cannot be interpreted. To pin it explicitly:

git config --global fuzgit.lang ja   # persistent (--local sets it per repository; `en` pins English)
gz --lang ja branch                  # one-off, accepted by every subcommand

fuzgit が表示するすべての文言(メッセージ・エラー・finder のヘッダー・ 確認プロンプト・--help)が日本語と英語に 対応しています。表示言語はロケールから自動的に決まり、 日本語ロケールでない場合や解釈できない場合は英語に なります。明示的に固定するには次のように指定します。

git config --global fuzgit.lang ja   # 永続設定(--local ならリポジトリごと。`en` なら英語に固定)
gz --lang ja branch                  # 単発の上書き。全サブコマンドで指定できる
The first layer that decides the language wins; the layers below it are not consulted. 先に決まった層より下は参照しません。
Priority優先 Source取得元 Meant for用途
1 --lang <ja|en|auto> A one-off override. A global option, accepted by every subcommand; there is no short form. 単発の上書き。全サブコマンド共通のグローバルオプションで、短縮形はありません。
2 FUZGIT_LANG Per shell or session. シェル・セッション単位の上書き。
3 git config fuzgit.lang The persistent setting. system / global / local / worktree all apply exactly as they do for git itself. 永続設定。system / global / local / worktree の階層が git 本体と同じように効きます。
4 LC_ALL → LC_MESSAGES → LANGUAGE → LANG Read from the environment, in the POSIX and gettext order. 環境からの自動判定(POSIX / gettext と同じ順)。
5 fallbackフォールバック en
  • Layers 1–3 are strict. They are explicit instructions to fuzgit, so any value other than ja, en or auto stops with an error instead of quietly falling back. An empty value counts as “not set” and resolution moves to the next layer. If reading fuzgit.lang fails altogether, fuzgit stops as well.
  • Layer 4 is lenient. Locale variables describe the environment rather than instruct fuzgit, so a value it cannot interpret — C and POSIX included — is not an error; it simply means “not Japanese” and the fallback applies.
  • auto means “skip the remaining explicit layers and read the environment”, wherever it appears in layers 1–3.
  • There is no fuzgit configuration file. The setting lives in git’s own fuzgit.lang key, read in-process with gix — no git process is started to resolve the language.
  • The language resolves outside a repository too, so gz --help and the “not a git repository” error are translated as well; system and global settings still apply there. One exception: a fuzgit.lang written under a conditional include (includeIf) in ~/.gitconfig is not read when resolving outside a repository.
Two limits

git’s own messages may stay English. fuzgit tells every git command which language to speak, but whether a catalog exists depends on how git was built (NLS) and on the installed locale data — and git upstream ships no Japanese catalog. Forcing en is always safe: it just asks git for its original text.

Text clap prints on its own stays English — Usage:, Options:, Commands: and parser errors, because clap 4 has no localization hook. fuzgit’s own --help descriptions do switch.

Output that fuzgit parses is a separate matter: those git calls always run with LC_MESSAGES=C, whatever the display language, so parsing never depends on the environment. Debugging shows how the two are told apart in the log.

  • 層 1〜3 は厳格です。fuzgit への明示的な指定であるため、 ja / en / auto 以外の値は 黙って既定へ倒さずエラーで停止します。空文字は「未設定」として扱い、 次の層へ進みます。fuzgit.lang の読み取り自体に失敗した場合も 停止します。
  • 層 4 は寛容です。ロケール環境変数は fuzgit への指示ではなく 環境の記述であるため、解釈できない値(C / POSIX を 含む)はエラーにせず「日本語ではない」と判定してフォールバックへ進みます。
  • auto は層 1〜3 のどこに現れても「以降の明示指定を飛ばして 環境からの自動判定へ進む」という意味です。
  • fuzgit 独自の設定ファイルはありません。設定は git config の fuzgit.lang に置き、gix でプロセス内から読みます (言語の解決で git プロセスは起動しません)。
  • 言語はリポジトリの外でも解決できます。 gz --help や「git リポジトリではありません」のエラーも 翻訳対象で、system / global の設定はそのまま効きます。ただし ~/.gitconfig の条件付き include(includeIf)配下に 書いた fuzgit.lang は、リポジトリ外での解決では読まれません。
2 つの制約

git 本体のメッセージは英語のままになることがあります。 fuzgit は起動するすべての git へ言語を伝えますが、翻訳の有無は git の ビルド(NLS)とロケールデータ次第です。とくに git 本体には日本語カタログがありません。 en への統一は git に原文を出させるだけなので、環境に 依存せず成立します。

clap が自前で出す固定文言は英語のままです。 Usage: / Options: / Commands: と パーサエラーが該当します(clap 4 に多言語化の機構がないため)。 --help のうち fuzgit 自身の文言は切り替わります。

fuzgit がパースする git の出力は別扱いで、表示言語に関わらず 常に LC_MESSAGES=C で実行します(環境によってパースが壊れない ようにするため)。ログ上での見分け方は デバッグを参照してください。

Configuration設定 #

fuzgit has no configuration file of its own. It borrows git’s config, so the usual system / global / local / worktree layers apply, and every key is optional.

fuzgit 独自の設定ファイルはありません。git config を 間借りするため、system / global / local / worktree の階層がそのまま効きます。 いずれのキーも任意です。

git config keygit config キー Value値 Default既定 What it does効果
fuzgit.lang ja / en / auto en Display language — see Language 表示言語 — 表示言語参照
fuzgit.notify boolean真偽値 false Desktop notification when a long run finishes — see Completion notifications 長い実行の完了通知 — 完了通知参照
fuzgit.fetchJobs integer ≥ 11 以上の整数 4 How many repositories gz fetch --siblings fetches at once (1 is fully serial) — see gz fetch gz fetch --siblings の同時実行数(1 で完全な直列)— gz fetch参照
Environment variable環境変数 What it does効果
FUZGIT_LANG Display language for one shell or session; beats fuzgit.lang, loses to --lang シェル・セッション単位の表示言語。fuzgit.lang より優先し、--lang に劣後する
FUZGIT_DEBUG=1 Log every git command fuzgit runs — see Debugging fuzgit が実行した git コマンドをログへ出す — デバッグ参照
NO_COLOR Turn the colouring of gz fetch / gz pull / gz worktree add / gz branch off. Colour is only ever written to a terminal, and stdout and stderr are judged separately, so a pipe or a redirect is unaffected either way gz fetch / gz pull / gz worktree add / gz branch の着色を止める。色は端末にしか書かず、標準出力と標準エラーは別々に判定するため、パイプ・リダイレクトでは元から影響しない
Invalid values stop the run

A value fuzgit cannot interpret is an error, not a silent fallback to the default — a misspelled fuzgit.fetchJobs stops before a single repository is fetched. An empty value is the one exception: it counts as “not set”.

不正な値は実行を止めます

解釈できない値は既定へ暗黙に倒さずエラーにします — 綴りを誤った fuzgit.fetchJobs は 1 件も fetch しないまま停止します。 例外は空文字で、これは「未設定」として扱います。

gz branch — switch, create, delete and tidy up branches gz branch — ブランチの切替・作成・削除・整理 y/Nmulti-select複数選択 #

gz branch                  # pick a local branch and run git switch (the classic behaviour)
gz branch --all            # also list remote-tracking branches (origin/... and friends)
gz branch create <name>    # pick the starting point and create a new branch
gz branch delete           # pick branches and delete them
gz branch cleanup          # delete every merged branch in one go

With no subcommand, gz branch switches branches — that is the classic behaviour, and --all only widens the candidate list. create / delete / cleanup are separate management operations. Mixing the two is ambiguous, so clap rejects it: gz branch --all create x is an error.

Switching (no subcommand)

OptionDescription
-a, --allAlso list remote-tracking branches
  • The current branch is marked with a leading *, just like git branch.
  • The preview opens with a summary of the highlighted branch — ↑ commits ahead of its upstream in green, ↓ commits behind in red, then the upstream, the last commit date and its author. A count of zero is left uncoloured, so only real drift catches the eye. Underneath are the 50 most recent commits (git log --oneline --decorate). A branch whose details cannot be read keeps the log and drops the summary.
  • Picking a remote-tracking branch (origin/feature) runs git switch with the short name (feature), so git’s DWIM creates the tracking local branch.
  • git’s report after the switch is coloured. Uncommitted changes carried across show their status letter in colour — A green, M yellow, D red — while the path stays plain, and Switched to branch 'x' is green. Already on 'x' and the tracking lines that follow are dimmed, since nothing happened there. git splits this report across both streams, so stdout and stderr are judged for colour separately: gz branch > file leaves the file clean. Set NO_COLOR to turn it off.

gz branch create <name> — creating a branch

Argument / optionDescription
<NAME>Name of the branch to create (required positional argument)
--no-switchCreate the branch without switching to it
  • What you pick is the starting point: local branches, remote-tracking branches and tags. Each line is prefixed with its kind ([branch] in green, [tag] in yellow) because the two can share a name.
  • It runs git branch -- <name> <start point>, then switches to the new branch — the same as git switch -c. --no-switch creates it only, and prints the command to switch.
  • The name is a positional argument, because skim has no text input (see Out of scope). Validation is git’s check-ref-format; a name starting with - is protected with -- and then rejected by git.

gz branch delete — deleting branches

OptionDescription
-f, --forceDelete branches that are not merged either (git branch -D)
--into <BRANCH>Branch used as the merged/unmerged baseline (defaults to HEAD)
  • Candidates are local branches except the current branch and branches checked out in another worktree (git refuses to delete either).
  • A candidate line is <name> merged|unmerged <relative update time> tracking: <upstream>|no tracking branch. Only information that can be gathered for every branch at once is put on the line; details of the last commit are in the preview (git log --oneline).
  • Tab selects several branches.
Cannot be undone

A confirmation prompt ([y/N]) lists every target before anything runs. The default is git branch -d, merged branches only: one unmerged branch in the selection stops the whole thing before git branch runs, so it never deletes half the selection and then gives up. Only --force switches to git branch -D, and the prompt then names the unmerged branches you are about to lose.

gz branch cleanup — deleting merged branches in one go

OptionDescription
--into <BRANCH>Branch used as the merged baseline (defaults to HEAD)
  • Candidates are limited to merged branches, and they start out selected — except the trunk (use Tab to select or deselect).
  • After the confirmation prompt they are deleted with git branch -d. There is no --force — unmerged branches are gz branch delete --force’s job.
The trunk is never preselected

“Trunk” means the repository’s default branch (whatever refs/remotes/<remote>/HEAD points at) plus the conventional main and master. --into defaults to HEAD, so running this from a feature branch counts its ancestor main as merged — preselected, that would be two keystrokes from deleting your trunk. It stays in the list with a label explaining why it is unselected, and you can still select it deliberately.

gz branch                  # ローカルブランチから選んで git switch(従来どおりの切替)
gz branch --all            # リモート追跡ブランチ(origin/... 等)も候補に含める
gz branch create <name>    # 作成元を選んで新しいブランチを作る
gz branch delete           # ブランチを選んで削除する
gz branch cleanup          # merged なブランチを一括で削除する

サブコマンドなしの gz branch はブランチ切替です (--all は候補の範囲を広げるだけ)。 create / delete / cleanup は別系統の 管理操作で、両者の併用は意図が曖昧になるため clap が拒否します (gz branch --all create x はエラー)。

切替(サブコマンドなし)

オプション説明
-a, --allリモート追跡ブランチも候補に含める
  • 現在のブランチは git branch と同じく行頭の * で識別できます。
  • プレビューの冒頭には選択中ブランチの要約を表示します。upstream より進んでいる コミット数を ↑(緑)、遅れている数を ↓(赤)で示し、 続けて upstream・最終コミット日時・作者が並びます。0 件の側には色を付けないため、 実際に差があるブランチだけが目に留まります。その下が直近 50 件のコミットログ (git log --oneline --decorate)です。要約を取得できなかった ブランチはログだけを表示します。
  • リモート追跡ブランチ(origin/feature)を選ぶと短縮名(feature)で git switch するため、git の DWIM により追跡ローカルブランチが作成されます。
  • 切り替え後の git の報告に色が付きます。 持ち越した未コミットの変更は状態コードだけを着色し(A 緑、 M 黄、D 赤)、パスは無色のままにします。 Switched to branch 'x' は緑、 Already on 'x' と後続の追跡状況の行は 何も起きていないため Dim にします。git はこの報告を 2 つのストリームへ 振り分けるため、標準出力と標準エラーの着色は別々に判定します (gz branch > file のファイルに色は混ざりません)。 NO_COLOR で止められます。

gz branch create <name> — ブランチの作成

引数・オプション説明
<NAME>作成するブランチ名(必須の位置引数)
--no-switch切り替えずに作成だけ行う
  • 選ぶのは作成元です。候補はローカルブランチ・リモート追跡 ブランチ・タグで、同名が共存しうるため行頭に種別が付きます (ブランチは緑の [branch]、タグは黄の [tag])。
  • 実行するのは git branch -- <name> <作成元> で、続けて 作成したブランチへ切り替えます(git switch -c と同じ)。 --no-switch なら作成のみを行い、切り替えるコマンドを案内します。 annotated tag は解決済みの ID を渡し、git が対象コミットまで peel します。
  • 名前は位置引数です(skim にテキスト入力 UI が無いため。 スコープ外・非対応参照)。妥当性検証は git の check-ref-format に委ね、- 始まりの名前は -- で保護したうえで git が拒否します。

gz branch delete — ブランチの削除

オプション説明
-f, --forcemerged でないブランチも削除する(git branch -D)
--into <BRANCH>merged 判定の基準ブランチ(既定は HEAD)
  • 候補はローカルブランチのうち、現在のブランチと、他の worktree で チェックアウト中のブランチを除いたものです(どちらも git が削除を許可しないため)。
  • 候補行は <名前> merged|unmerged <相対更新日時> 追跡: <upstream>|追跡なし です。 いずれも全件を一括取得できる情報に限っており、最終コミットの詳細はプレビュー (git log --oneline)で確認します。
  • Tab で複数選択できます。
取り消せません

実行前に確認プロンプト([y/N])で対象を全件列挙します。既定は git branch -d(merged のみ)で、unmerged が 1 件でも 含まれると git branch の実行前に停止します (一部だけ削除して止まることはありません)。--force を 指定したときだけ git branch -D を使い、確認プロンプトが 失われる unmerged ブランチを名指しします。

gz branch cleanup — merged なブランチの一括削除

オプション説明
--into <BRANCH>merged 判定の基準ブランチ(既定は HEAD)
  • 候補を merged のブランチのみに絞り、幹を除いて選択済みの状態で 表示します(Tab で選択・解除できます)。
  • 確認プロンプトを経て git branch -d で一括削除します。 --force はありません(unmerged を扱うのは gz branch delete --force の役目です)。
幹は事前選択しません

「幹」とはリポジトリの既定ブランチ (refs/remotes/<remote>/HEAD が指す先)と、慣習的な main / master です。--into の既定が HEAD であるため、feature ブランチから実行すると祖先の main も merged に該当します。選択済みのままでは 2 打鍵で幹が 消えてしまうため、事前選択から外しています。候補一覧には理由のラベル付きで 残るので、本当に消したい場合は自分で選択できます。

gz log — exploring the commit history gz log — コミット履歴の探索 #

gz log                 # 1000 commits by default
gz log --limit 200     # limit how many commits are listed (-n also works)
git show "$(gz log)"   # use the full hash of the picked commit directly
gz log --action        # pick a commit, then pick what to do with it
OptionDefaultDescription
-n, --limit <N>1000Maximum number of commits to list
--actionoffAfter picking a commit, show a menu of what to do with it
  • Each candidate is one line, <short hash> <date> <summary> (<author>), and that whole string is what you filter on.
  • The preview is git show --color=always (commit details and diff).
  • On confirmation only the full hash of the picked commit goes to stdout, for pipes and copying. Messages go to stderr, so $(gz log) is safe.
  • --action opens a second menu after the commit is picked: show the commit, switch to it as a detached HEAD, cherry-pick, revert, create a fixup commit, and print the full hash. The cherry-pick, revert, and fixup entries run the same code as gz cherry-pick / gz revert / gz fixup, so their safeguards still apply.
Nothing changes without --action

The default output is byte for byte what it has always been, so git show "$(gz log)" keeps working. There is no reset --hard entry in the menu: these commits are ancestors of HEAD, and revert covers the same need without discarding anything.

gz log                 # 既定 1000 件を候補にする
gz log --limit 200     # 取得件数を制限する(-n でも可)
git show "$(gz log)"   # 選んだコミットのフルハッシュをそのまま使う
gz log --action        # コミットを選んで、続けて行う操作も選ぶ
オプション既定値説明
-n, --limit <N>1000取得するコミットの最大件数
--actionoffコミットを選んだあと、続けて行う操作をメニューから選ぶ
  • 候補は <短縮ハッシュ> <日付> <サマリ> (<作者>) の 1 行で、この文字列がそのまま絞り込み対象です。
  • プレビューは git show --color=always(コミットの詳細と差分)。
  • 決定すると選択コミットのフルハッシュだけを標準出力へ出します (パイプ・コピー用途)。メッセージ類は標準エラーへ出すため、$(gz log) で 安全に受け取れます。
  • --action を付けると、コミットを選んだあとに 2 つ目のメニューが出ます — 詳細を表示する・detached HEAD で切り替える・ cherry-pick する・revert する・fixup コミットを作る・ フルハッシュを出力する。cherry-pick / revert / fixup は gz cherry-pick / gz revert / gz fixup と 同じ実装を呼ぶため、各コマンドの安全策がそのまま働きます。
--action を付けなければ挙動は変わりません

既定の標準出力は従来と 1 バイトも変わらないため、 git show "$(gz log)" はそのまま動きます。メニューに reset --hard はありません — gz log の候補は HEAD の祖先であり、同じ用途は破棄を伴わない revert が 満たすためです。

gz cherry-pick — picking commits to cherry-pick gz cherry-pick — cherry-pick するコミットの選択 multi-select複数選択 #

gz cherry-pick                     # commits from every branch
gz cherry-pick --branch feature    # restrict to one branch
OptionDescription
-b, --branch <BRANCH>Target branch (without it, commits from all branches are listed)
  • Tab selects several commits. Whatever order you picked them in, they are always cherry-picked oldest first (history order).
  • The candidate limit is 1000, the same as the default of gz log --limit.
  • On a conflict, git’s message is shown as-is and the exit code is non-zero. Continue or abort with git cherry-pick --continue / git cherry-pick --abort.
gz cherry-pick                     # 全ブランチのコミットを候補にする
gz cherry-pick --branch feature    # 対象ブランチを指定する
オプション説明
-b, --branch <BRANCH>対象ブランチ(未指定時は全ブランチのコミットを候補にする)
  • Tab で複数選択できます。選んだ順に関わらず、常に古い順(履歴順)に cherry-pick します。
  • 候補件数の上限は gz log --limit の既定値と同じ 1000 件です。
  • コンフリクト時は git のメッセージをそのまま表示して非ゼロ終了します。 続行・中止は git cherry-pick --continue / git cherry-pick --abort で行ってください。

gz restore — picking files to restore or unstage gz restore — 復元・アンステージするファイルの選択 y/Nmulti-select複数選択 #

gz restore                       # discard working tree changes (with confirmation)
gz restore --staged              # unstage staged changes
gz restore --source HEAD~1       # overwrite with the content of a revision (with confirmation)
OptionDescription
-s, --source <REV>Revision to restore from; the file list of that commit becomes the candidate list
-S, --stagedUnstage staged changes
  • Tab selects several files. Candidates carry the same status codes as git status. They are left-aligned and coloured like git’s.
  • The preview is the diff of the file (with --staged, the diff between HEAD and the index).
Overwrites your edits

Anything that rewrites the working tree asks [y/N] first, listing the files it is about to overwrite. Unstaging with --staged touches only the index, so it runs without a prompt.

gz restore                       # 作業ツリーの変更を破棄する(確認あり)
gz restore --staged              # ステージ済みの変更をアンステージする
gz restore --source HEAD~1       # 指定リビジョンの内容で上書きする(確認あり)
オプション説明
-s, --source <REV>復元元のリビジョン。指定するとそのコミットのファイル一覧が候補になる
-S, --stagedステージ済みの変更をアンステージする
  • Tab で複数選択できます。候補には git status と 同じ状態コードが付きます。表示は git と同じ配色で左詰めです。
  • プレビューは対象ファイルの差分(--staged 指定時は HEAD とインデックスの差分)。
編集内容を上書きします

作業ツリーを書き換える操作は、上書き対象を列挙したうえで [y/N] を確認します。--staged による アンステージはインデックスしか触らないため、確認せずに実行します。

gz add — picking files to stage gz add — ステージするファイルの選択 multi-select複数選択 #

gz add
  • Candidates are unstaged changes and untracked files (files that are already staged with no remaining difference do not appear), each line prefixed with the same status code as git status. They are left-aligned and coloured like git’s.
  • Tab selects several files.
  • The preview is the diff for tracked files, and the file content (up to the first 64 KiB) for untracked files.
  • 候補は未ステージの変更と未追跡ファイル(既にステージ済みで差分の無いファイルは 出ません)で、行頭には git status と同じ状態コードが付きます。表示は git と同じ配色で左詰めです。
  • Tab で複数選択できます。
  • プレビューは追跡ファイルは差分、未追跡ファイルはファイルの内容(先頭 64KiB まで)。

gz stash — creating, searching and restoring stashes gz stash — stash の作成・検索・復元 y/Nmulti-select複数選択 #

push picks files; apply / pop / drop pick an existing stash. They are separate subcommands because what you pick differs. Bare gz stash prints the subcommand help rather than assuming one of them.

gz stash push                      # pick from the tracked changes and stash them
gz stash push -m "work in progress"  # add a message
gz stash push -u                   # also list untracked files
gz stash apply                     # apply the picked stash (the stash stays)
gz stash pop                       # apply it and drop that stash
gz stash drop                      # discard the picked stash (with confirmation)
SubcommandOptionDescription
push-m, --message <MESSAGE>Message attached to the stash
apply / pop / drop(none)Pick one entry from the stash list
  • push supports multi-select with Tab, and only the files you picked are stashed (the rest stay in the working tree). Staged changes are stashed too, so the preview is the diff against HEAD (git diff HEAD). Files you have just created are listed as well — no git add first.
  • Candidates for apply / pop / drop are stash@{n}: <message>, so you can filter by message. The preview has two sections: files (which paths the stash holds and how much each changed) and diff underneath. Both ask git for --include-untracked, so a stash made only of brand-new files is not a blank pane.
Cannot be undone

drop discards a stash for good, so it asks [y/N] first. pop drops the stash too, but only after the apply succeeds.

push が選ぶのは「ファイル」、apply / pop / drop が選ぶのは既存の「stash」です。 選択対象が異なるため、フラグではなくサブコマンドで分けています。 引数なしの gz stash は、どちらかへ暗黙に倒さずヘルプを表示します。

gz stash push                      # 追跡済みの変更から選んで退避する
gz stash push -m "作業中"          # メッセージを付ける
gz stash push -u                   # 未追跡ファイルも候補に含める
gz stash apply                     # 選んだ stash を適用する(stash は残る)
gz stash pop                       # 適用して、その stash を取り除く
gz stash drop                      # 選んだ stash を破棄する(確認あり)
サブコマンドオプション説明
push-m, --message <MESSAGE>stash に付けるメッセージ
apply / pop / drop(なし)stash 一覧から 1 件選ぶ
  • push は Tab で複数選択でき、選んだファイルだけが 退避されます(選ばなかった変更は作業ツリーに残ります)。ステージ済みの変更も 退避対象になるため、プレビューは HEAD との差分(git diff HEAD)です。 作ったばかりのファイルも候補に並びます(先に git add する必要はありません)。
  • apply / pop / drop の候補は stash@{n}: <メッセージ> で、メッセージで絞り込めます。 プレビューは 2 段構成で、上に files(含まれるパスと変更量)、 下に diff が出ます。どちらも --include-untracked 付きで取るため、新規ファイルだけを 退避した stash でも空にはなりません。
取り消せません

drop は stash を完全に破棄するため、実行前に [y/N] を確認します。pop も stash を取り除きますが、 適用が成功したあとに限られます。

gz reflog — investigating deleted branches gz reflog — 削除済みブランチの調査 y/N #

gz reflog                            # print the commit hash of the picked entry to stdout
gz reflog --restore recovered        # create a new branch `recovered` from the picked commit
gz reflog --action                   # pick an entry, then pick what to do with it
OptionDescription
--restore <NAME>Create a new branch with the given name from the picked commit
--actionAfter picking an entry, show a menu of what to do with it (cannot be combined with --restore)
  • Candidates are <short hash> HEAD@{n}: <message>, so you can filter on messages such as checkout: moving from ....
  • By default only the full hash goes to stdout. The result of --restore goes to stderr, leaving stdout free for pipes.
  • --action opens a second menu: show the commit, switch to it as a detached HEAD, cherry-pick, reset the current branch back to it, and print the full hash.
  • --restore is not deprecated, and the two flags cannot be combined: use --restore when the operation needs a name you have to type — a finder has no text input — and --action for everything else.
  • An entry can point at an object git gc has already removed. Those are listed on purpose; acting on one fails with git’s own message and a non-zero exit.
Reset discards commits

Reset the current branch back to it is the one destructive entry in the --action menu. It asks for an explicit y/N and runs no git at all if you decline.

gz reflog                            # 選んだエントリのコミットハッシュを標準出力へ出す
gz reflog --restore recovered        # 選んだコミットから新規ブランチ `recovered` を作成する
gz reflog --action                   # エントリを選んで、続けて行う操作も選ぶ
オプション説明
--restore <NAME>選択したコミットから指定名の新規ブランチを作成する
--actionエントリを選んだあと、続けて行う操作をメニューから選ぶ(--restore とは同時に指定できません)
  • 候補は <短縮ハッシュ> HEAD@{n}: <メッセージ> で、checkout: moving from ... 等のメッセージで絞り込めます。
  • 既定ではフルハッシュだけを標準出力へ出します。--restore の 実行結果は標準エラーへ出るため、標準出力はパイプ用途のまま空けてあります。
  • --action を付けると 2 つ目のメニューが出ます — 詳細を表示する・detached HEAD で切り替える・ cherry-pick する・現在のブランチをこのコミットへ戻す・ フルハッシュを出力する。
  • --restore は非推奨ではありません(両者は同時に 指定できません)。finder にテキスト入力が無いため、入力が必要な名前を 渡すなら --restore、名前を要さない操作なら --action と使い分けてください。
  • エントリは git gc で実体を失っている場合があります(意図的に候補へ 出しています)。操作を実行すると git 自身のエラーがそのまま出て非ゼロ終了します。
reset はコミットを捨てます

--action のメニューで破壊的なのは 現在のブランチをこのコミットへ戻すだけです。実行前に y/N の明示同意を求め、同意が得られなければ git を一切実行しません。

gz commit — picking the files to commit gz commit — コミットするファイルの選択 multi-select複数選択 #

gz commit                      # commit only the files you picked (message via the editor)
gz commit -m "fix authentication"   # give the message directly (no editor)
OptionDescription
-m, --message <MESSAGE>Commit message (without it, git opens the editor)
  • Candidates are staged, unstaged and untracked changes in one single list, each line prefixed with the same status code as git status. They are left-aligned and coloured like git’s.
  • Files with staged changes start out selected. Tab deselects them, and a deselected entry never comes back when you keep typing a filter.
  • It runs a path-limited commit, git commit -- <paths>..., so changes you did not pick are not committed even if they were staged — they simply stay staged. Untracked files you picked are git added first, since a path-limited commit cannot target them.
  • The preview is the diff against HEAD, not against the index, because that is what a path-limited commit records (git diff --color=always HEAD -- <path>; file content for untracked files).
  • With no changes at all it prints “There are no candidates to select from” and exits.
GUI editors need --wait

Without -m, the message is entered through git commit — your core.editor. A GUI editor configured without a wait flag exits immediately, so git sees an empty message and aborts with Aborting commit due to empty commit message. Plain git commit behaves the same way.

git config --global core.editor "code --wait"   # example for VS Code

-m sidesteps it entirely by never starting an editor. fuzgit prints this hint to stderr when a commit fails.

gz commit                      # 選んだファイルだけをコミットする(メッセージはエディタで入力)
gz commit -m "認証を修正する"   # メッセージを直接指定する(エディタを起動しない)
オプション説明
-m, --message <MESSAGE>コミットメッセージ(省略時は git がエディタを起動する)
  • 候補はステージ済み・未ステージ・未追跡の変更を1 つの一覧にしたもので、 行頭には git status と同じ状態コードが付きます。表示は git と同じ配色で左詰めです。
  • ステージ済みの変更があるファイルは起動時から選択済みです。 Tab で選択を外せます(外した選択が絞り込みのたびに復活することはありません)。
  • 実行するのは git commit -- <paths>... のパス指定コミットです。 そのため選ばなかった変更はステージ済みであってもコミットされず、 ステージ状態のまま残ります。選んだ未追跡ファイルは、パス指定コミットの対象に できないため先に git add します。
  • プレビューは index ではなく HEAD との差分です(パス指定コミットが記録するのは 作業ツリーの内容であるため。git diff --color=always HEAD -- <path>。 未追跡ファイルは内容)。
  • 変更が 1 件も無い場合は「選択できる候補がありません」と表示して終了します。
GUI エディタには --wait が必要です

-m を省略すると、メッセージの入力は git commit (= core.editor)に委ねられます。終了待ちなしで GUI エディタを設定していると、エディタが即座に終了するため git は メッセージが空だと判断し、 Aborting commit due to empty commit message. で中止されます (素の git commit でも同じです)。

git config --global core.editor "code --wait"   # VS Code の例

-m を指定すればエディタを起動しないため回避できます。 コミットが失敗したときは、この内容をヒントとして標準エラーへ表示します。

gz fixup — picking the target of a fixup commit gz fixup — fixup コミットの対象選択 #

gz fixup            # git commit --fixup=<picked commit>
gz fixup --squash   # git commit --squash=<picked commit>
OptionDescription
--squashCreate a squash commit (messages are combined) instead of a fixup commit
  • What gets committed is the staged changes. With nothing staged it exits before the finder even opens, telling you to stage something with gz add first.
  • Candidates are commits reachable from HEAD (1000 by default, shared with gz log --limit). The preview is git show --color=always.
  • Afterwards the step that folds the commit into history is printed to stderr. The rebase is never run for you — rewriting history stays an explicit action.
    Hint: run the following to fold the new commit into the history:
      git rebase -i --autosquash <full hash of the picked commit>^
    For the very first commit there is no ^ to resolve, so the starting point becomes --root, with the reason printed alongside.
  • --squash makes git open the editor to append the message body (--fixup does not).
gz fixup            # git commit --fixup=<選んだコミット>
gz fixup --squash   # git commit --squash=<選んだコミット>
オプション説明
--squashfixup ではなく squash コミット(メッセージを結合する)を作成する
  • コミット対象はステージ済みの変更です。ステージ済みの変更が無い場合は、 finder を起動する前に「ステージ済みの変更がありません」と表示して終了します。
  • 候補は HEAD からのコミット(既定 1000 件、gz log --limit の既定値と共通)。 プレビューは git show --color=always。
  • 実行後、履歴へ取り込む手順を標準エラーへ表示します。 rebase は自動実行しません(履歴改変は明示操作に委ねます)。
    ヒント: 作成したコミットを履歴へ取り込むには次を実行してください。
      git rebase -i --autosquash <選んだコミットのフルハッシュ>^
    最初のコミットには解決できる ^ が無いため、起点は --root になります(理由も併記します)。
  • --squash は git がメッセージ本文の追記のためにエディタを起動します(--fixup は起動しません)。

gz merge — picking the branch to merge gz merge — merge するブランチの選択 y/N #

gz merge             # a normal merge (fast-forwards when it can)
gz merge --no-ff     # always create a merge commit
gz merge --squash    # only apply the result to the working tree and index, without committing
gz merge --ff-only   # merge only when it can fast-forward
OptionDescription
--no-ffCreate a merge commit even when a fast-forward is possible
--squashApply the merge result to the working tree and index without committing
--ff-onlyMerge only when a fast-forward is possible
  • The three options are mutually exclusive (combining them is rejected before the selection starts).
  • The merge destination is fixed to the current position (HEAD); candidates are local and remote-tracking branches excluding the current branch.
  • The preview is the commits that would be pulled in (git log --oneline HEAD..<candidate>, up to 50).
  • A confirmation prompt ([y/N]) comes first, listing how many commits will be merged, the exact command that will run, and the conflict prediction.
  • On a conflict, git’s message is shown as-is and the exit code is non-zero. Resolve it with the recovery menu (run gz merge again) or with plain git.
About the conflict prediction

It is a dry run of git merge-tree --write-tree and needs Git 2.38 or newer. When it cannot be produced the prompt says “Conflict prediction: skipped” and the merge still runs — “could not predict” is never reported as “will not conflict”.

gz merge             # 通常の merge(fast-forward できる場合は fast-forward)
gz merge --no-ff     # fast-forward できる場合でもマージコミットを作る
gz merge --squash    # 結果を作業ツリー・index へ反映するだけでコミットしない
gz merge --ff-only   # fast-forward できる場合のみ merge する
オプション説明
--no-fffast-forward できる場合でもマージコミットを作成する
--squashマージ結果を作業ツリー・index へ反映するだけでコミットしない
--ff-onlyfast-forward できる場合のみ merge する
  • 3 つのオプションは相互排他です(同時に指定すると選択を始める前に拒否されます)。
  • merge 先は現在の位置(HEAD)に固定で、候補は現在のブランチを除くローカルブランチとリモート追跡ブランチです。
  • プレビューは取り込まれるコミット(git log --oneline HEAD..<候補>、最大 50 件)。
  • 実行前に確認プロンプト([y/N])を表示します。 取り込まれるコミット数・実際に実行するコマンド・コンフリクト予測を提示します。
  • コンフリクトした場合は git のメッセージがそのまま表示され、非ゼロ終了します。 解決は復帰メニュー(gz merge を再実行)または 素の git で行います。
コンフリクト予測について

git merge-tree --write-tree のドライランであり、 Git 2.38 以降が必要です。予測できない場合は「省略しました」と 表示し、merge の実行は継続します。「予測できなかった」を 「コンフリクトしない」として扱うことはありません。

gz rebase — picking the rebase base gz rebase — rebase の base の選択 y/N #

gz rebase   # pick the base and run git rebase <base>
  • Candidates are local and remote-tracking branches excluding the current branch (tags and arbitrary commits are not offered).
  • The preview is the commits that would be replayed (git log --oneline <candidate>..HEAD, up to 50).
  • On a conflict, git’s message is shown as-is and the exit code is non-zero.
Rewrites history

A confirmation prompt ([y/N]) comes first, stating how many commits will be replayed and that their hashes will change. Anyone who has already pulled those commits will have to deal with the rewrite.

gz rebase   # base を選んで git rebase <base>
  • 候補は現在のブランチを除くローカルブランチとリモート追跡ブランチです(tag や任意コミットは候補に含めません)。
  • プレビューは replay されるコミット(git log --oneline <候補>..HEAD、最大 50 件)。
  • コンフリクトした場合は git のメッセージがそのまま表示され、非ゼロ終了します。
履歴を書き換えます

実行前に確認プロンプト([y/N])を表示し、replay されるコミット数と コミットハッシュが変わることを提示します。そのコミットを取得済みの相手には、 書き換えの影響が及びます。

The recovery menu for conflicts (gz merge / gz rebase / gz pull) コンフリクト時の復帰メニュー(gz merge / gz rebase / gz pull) y/N #

Running gz merge / gz rebase / gz pull while a merge or rebase is in progress shows a recovery menu instead of the usual flow. Its contents are decided by the operation that is in progress (running gz merge during a rebase shows the rebase menu).

In progressMenu entries
merge Inspect the conflicted files and mark them resolved / resume the merge (git merge --continue) / abort the merge (git merge --abort)
rebase Inspect the conflicted files and mark them resolved / resume the rebase (git rebase --continue) / skip the current commit (git rebase --skip) / abort the rebase (git rebase --abort)
  • Whichever entry is highlighted, the preview shows the current state (git status --short --branch), so you can tell unresolved (UU) from resolved (M ) on the spot.
  • Choosing “inspect the conflicted files and mark them resolved” lists the unmerged files. The preview shows the diff including conflict markers (<<<<<<< and friends), and the files you pick with Tab are git added (staged as resolved). Editing the files themselves is up to your own editor — fuzgit never launches one.
  • If you choose that entry with no unresolved files left, it tells you that you can resume with continue and exits.
  • continue / skip run with inherited stdio, so git opens the editor when a commit message is needed.
  • There is no recovery menu for cherry-pick or revert (that is left to git’s own --continue / --abort guidance).
Abort throws away your resolutions

Aborting rolls the working tree back to before the merge or rebase started, losing every conflict you have resolved since. A confirmation prompt ([y/N]) comes first.

merge / rebase が進行中の状態で gz merge / gz rebase / gz pull を実行すると、通常のフローではなく 復帰メニューが表示されます。メニューの内容は 進行中の操作の種類で決まります(rebase 進行中に gz merge を実行しても rebase のメニューが出ます)。

進行中メニュー項目
merge コンフリクトファイルを確認して解決済みにする / merge を再開する(git merge --continue)/ merge を中止する(git merge --abort)
rebase コンフリクトファイルを確認して解決済みにする / rebase を再開する(git rebase --continue)/ 現在のコミットを飛ばす(git rebase --skip)/ rebase を中止する(git rebase --abort)
  • どの項目を選んでいても、プレビューには現在の状態 (git status --short --branch)が出ます。未解決(UU 等)と 解決済み(M )の区別をその場で確認できます。
  • 「コンフリクトファイルを確認して解決済みにする」を選ぶと、コンフリクト中(unmerged)の ファイル一覧が出ます。プレビューにはコンフリクトマーカー (<<<<<<< 等)を含む差分が表示され、 Tab で選んだファイルを git add(=解決済みとして stage)します。 ファイルの編集そのものは各自のエディタで行ってください(fuzgit はエディタを起動しません)。
  • 未解決のファイルが 1 件も無い状態でこの項目を選んだ場合は、continue で再開できる旨を表示して終了します。
  • continue / skip は継承 stdio で実行するため、コミットメッセージの入力が必要な場合は git がエディタを起動します。
  • cherry-pick / revert には復帰メニューを用意していません(git の --continue / --abort の案内に委ねます)。
中止すると解決内容が失われます

中止(abort)は merge / rebase を始める前の状態まで戻すため、それまでに 解決したコンフリクトはすべて失われます。実行前に確認プロンプト ([y/N])を表示します。

gz revert — picking the commits to undo gz revert — 打ち消すコミットの選択 multi-select複数選択 #

gz revert             # revert the picked commits (message reviewed in the editor)
gz revert --no-edit   # commit with git's default message, without opening the editor
OptionDescription
--no-editCommit with git’s default message without opening the editor
  • Candidates are commits reachable from HEAD (1000 by default, shared with gz log --limit). The preview is git show --color=always.
  • Tab selects several commits. Whatever order you picked them in, they are reverted newest first (undoing an old commit first tends to clash with the later changes stacked on top of it — exactly the opposite of gz cherry-pick).
  • Picking several commits opens the editor once per commit unless you pass --no-edit.
  • On a conflict, git’s message is shown as-is and the exit code is non-zero. Continue or abort with git revert --continue / git revert --abort.
Merge commits stop the run

Reverting a merge commit needs -m <parent-number>, and picking the mainline is not something a fuzzy finder improves on. So a merge commit in the selection stops fuzgit before git revert runs — and it hands you a ready-to-run git revert -m 1 <full hash>. Merge commits are never silently dropped from the candidate list.

gz revert             # 選んだコミットを revert する(メッセージはエディタで確認)
gz revert --no-edit   # エディタを起動せず git の既定メッセージのままコミットする
オプション説明
--no-editエディタを起動せず、git の既定メッセージのままコミットする
  • 候補は HEAD からのコミット(既定 1000 件、gz log --limit の既定値と共通)。プレビューは git show --color=always。
  • Tab で複数選択できます。選んだ順に関わらず、常に新しい順に revert します(古いコミットを先に打ち消すと、その上に積まれた後続の変更と衝突しやすいため。 gz cherry-pick の古い順とはちょうど逆になります)。
  • 複数件を選ぶと --no-edit を付けない限りエディタが件数分だけ開きます。
  • コンフリクトした場合は git のメッセージをそのまま表示して非ゼロ終了します。 続行・中止は git revert --continue / git revert --abort で行ってください。
マージコミットは実行前に停止します

マージコミットの revert には -m <parent-number> が必要ですが、 mainline の選択は「選ぶ」価値が乗らないため非対応です。選択に含まれる場合は git revert を実行する前に停止し、 git revert -m 1 <フルハッシュ> の形でそのまま実行できる コマンドを提示します(暗黙に候補から除外することはしません)。

gz status — the state dashboard (two-step selection) gz status — 状態ダッシュボード(2 段選択) multi-select複数選択 #

gz status

There are no options. It is a two-step flow: pick the files, then pick the action.

1. File selection (multi-select)

  • Candidates are staged, unstaged and untracked changes in one list (the same candidate generation as gz commit), each line prefixed with the same status code as git status. They are left-aligned and coloured like git’s.
  • The header summarises the current state in one line:
    main  |  ahead 2 / behind 1  |  staged 2 / unstaged 2 / untracked 1 / stash 1
    Without an upstream the whole ahead/behind block is omitted (on a detached HEAD, detached HEAD is shown instead of the branch name). ahead/behind is computed from the local remote-tracking ref, so it uses no network.
  • The preview is split into per-state sections: staged (git diff --cached) when there are staged changes and unstaged (git diff) when there are unstaged ones; sections that do not apply do not run git at all. Untracked files show their content as-is.

2. Action menu (single select)

EntryWhat it runs
Stage the selected files (git add)Same as gz add
Discard the changes of the selected files (git restore)Same as gz restore (with a confirmation prompt)
Stash the selected files (git stash push)Same as gz stash push (--include-untracked is added when untracked files were picked)
Commit the selected files (git commit)Same as gz commit (untracked files are git added first; message via the editor)
Print the paths of the selected files to stdoutFor pipes. Nothing but the paths is printed
  • Each action is literally the same implementation as the corresponding command, so its safeguards (restore’s confirmation prompt, commit’s pre-add of untracked files) keep working.
  • The menu preview shows the current state (git status --short --branch).
  • Every message except “print the paths to stdout” goes to stderr, so stdout stays free for pipes.
  • With no changes at all it is not an error: it exits with code 0 (checking that the tree is clean is a legitimate use of status). In that case the header-equivalent information goes to stderr.
    $ gz status
    There are no changes (the working tree is clean)
    main  |  staged 0 / unstaged 0 / untracked 0 / stash 0

オプションはありません。ファイルを選ぶ → アクションを選ぶの 2 段構成です。

1. ファイル選択(複数選択)

  • 候補はステージ済み・未ステージ・未追跡の変更を 1 つの一覧にしたもの (gz commit と同じ候補生成)で、行頭には git status と 同じ状態コードが付きます。表示は git と同じ配色で左詰めです。
  • ヘッダーに現在の状態を 1 行で表示します。
    main  |  ahead 2 / behind 1  |  staged 2 / unstaged 2 / untracked 1 / stash 1
    upstream が設定されていない場合、ahead / behind の区画ごと省略します (detached HEAD ではブランチ名の代わりに detached HEAD と表示します)。 ahead / behind はローカルのリモート追跡参照から算出するため ネットワークを使いません。
  • プレビューは状態別のセクションに分かれます。ステージ済みの変更があれば staged(git diff --cached)、未ステージの変更があれば unstaged(git diff)を表示し、該当しないセクションは git を実行しません。未追跡ファイルは内容をそのまま表示します。

2. アクションメニュー(単一選択)

項目実行内容
選択したファイルをステージする (git add)gz add と同じ
選択したファイルの変更を破棄する (git restore)gz restore と同じ(確認プロンプトあり)
選択したファイルを stash へ退避する (git stash push)gz stash push と同じ(未追跡を選んだ場合は --include-untracked が付く)
選択したファイルをコミットする (git commit)gz commit と同じ(未追跡ファイルは事前に git add、メッセージはエディタ)
選択したファイルのパスを標準出力へ出力するパイプ用途。パス以外は出力しない
  • 各アクションの実体は対応する既存コマンドと同じ実装であり、安全策 (restore の確認プロンプト、commit の未追跡ファイルの事前 add など)も そのまま働きます。
  • メニューのプレビューには現在の状態(git status --short --branch)を表示します。
  • 「パスを標準出力へ出力する」以外のメッセージ類はすべて標準エラーへ出すため、標準出力はパイプ用途に空いています。
  • 変更が 1 件も無い場合はエラーにせず、終了コード 0 で終わります (クリーンな状態の確認も status の用途のため)。この場合はヘッダー相当の情報を 標準エラーへ出します。
    $ gz status
    変更はありません(作業ツリーはクリーンです)
    main  |  staged 0 / unstaged 0 / untracked 0 / stash 0

gz diff — pick what to compare and show the diff gz diff — 比較対象を選んで差分表示 #

gz diff              # unstaged changes (same as git diff)
gz diff --staged     # staged changes (same as git diff --staged)
gz diff --head       # HEAD against the working tree (including staged changes)
gz diff --upstream   # HEAD against the upstream (the remote-tracking ref)
gz diff --branch     # pick two branches and compare them
gz diff --commit     # pick two commits and compare them
OptionDescription
--stagedTarget the staged changes (same as git diff --staged)
--headCompare HEAD with the working tree (including staged changes)
--upstreamCompare HEAD with the upstream
--branchPick two branches and compare them
--commitPick two commits and compare them
  • The comparison modes are mutually exclusive. The flag names follow git’s own vocabulary.
  • --branch / --commit open the fuzzy finder twice, for the left then the right side. The header tells you which one you are picking (1/2 branch to compare from, then 2/2 branch to compare to). Candidates and previews are shared with gz branch and gz log.
  • Once the range is fixed, the list of changed files appears so you can narrow it down with Tab. The header shows the chosen range, and the preview is the coloured diff limited to the highlighted file.
  • On confirmation it runs git diff <range> -- <pathspec>... with inherited stdio (pager and colour are left to git). Revision comparisons are passed as two arguments, git diff <a> <b>, not as <a>..<b>.
  • --upstream stops with a dedicated error when there is no upstream or on a detached HEAD. The right-hand side is the local remote-tracking ref (refs/remotes/<remote>/<branch>), so it uses no network. Run gz fetch first if you want to compare against the latest state.
  • When the range contains no changed files it is not an error: it exits with code 0.
    $ gz diff
    There is no difference (Unstaged changes: index and work tree)
gz diff              # 未ステージの変更(git diff と同じ)
gz diff --staged     # ステージ済みの変更(git diff --staged と同じ)
gz diff --head       # HEAD と作業ツリー(ステージ済みの変更も含む)
gz diff --upstream   # HEAD と upstream(リモート追跡参照)
gz diff --branch     # ブランチを 2 回選んで比較する
gz diff --commit     # コミットを 2 回選んで比較する
オプション説明
--stagedステージ済みの変更を対象にする(git diff --staged と同じ)
--headHEAD と作業ツリーを比較する(ステージ済みの変更を含む)
--upstreamHEAD と upstream を比較する
--branchブランチを 2 回選択して比較する
--commitコミットを 2 回選択して比較する
  • 比較モードは相互排他です。フラグ名は git 本体の語彙に合わせています。
  • --branch / --commit は比較元・比較先の順に fuzzy finder を 2 回起動します。どちらを選んでいるかはヘッダー (1/2 比較元のブランチ / 2/2 比較先のブランチ)で分かります。 候補・プレビューはそれぞれ gz branch / gz log と共通です。
  • 比較範囲が確定すると変更ファイル一覧が出るので、Tab で表示したいファイルを 絞り込めます。ヘッダーには確定した比較範囲を表示し、プレビューは選択中ファイルに 限定した色付き差分です。
  • 決定すると git diff <範囲> -- <pathspec>... を継承 stdio で 実行します(ページャ・色は git に委ねます)。リビジョン同士の比較は <a>..<b> ではなく git diff <a> <b> の 2 引数で渡します。
  • --upstream は upstream が設定されていない場合・detached HEAD の場合に 専用エラーで停止します。比較先はローカルのリモート追跡参照 (refs/remotes/<remote>/<branch>)であり、 ネットワークを使いません。最新の状態と比べたい場合は先に gz fetch を実行してください。
  • 比較範囲に変更ファイルが 1 件も無い場合はエラーにせず、終了コード 0 で終わります。
    $ gz diff
    差分はありません(未ステージの変更: index と作業ツリー)

gz fetch — deciding what to fetch gz fetch — fetch 対象の取得 network通信multi-select複数選択 #

gz fetch               # decide what (which remote) to fetch and run git fetch
gz fetch --prune       # also clean up tracking refs for branches deleted on the remote
gz fetch --siblings    # pick repositories sitting next to this one and fetch them all
OptionDescription
-p, --pruneDelete tracking refs for branches deleted on the remote (git fetch --prune)
-s, --siblingsAlso target repositories sitting at the same level as the current one (multi-select)

The point of this command is not “pick a remote” but “decide what to fetch”. The fuzzy finder only opens when there is actually a choice.

Default (the current repository)

  • The finder only opens when there is more than one remote. Candidates are the registered remote names plus a fixed entry, all remotes, which runs git fetch --all.
  • With exactly one remote the finder does not open and the fetch runs immediately, because choosing between the only remote and “all remotes” would contact the same place either way. So that you can still tell what was contacted, one line naming the target is printed to stderr before running (git fetch can print nothing at all when there are no updates).
    $ gz fetch
    Fetching from the remote `origin` (only one remote is registered, so the selection was skipped)
  • The preview only shows local information (two sections: the remote URL and the known remote-tracking branches). A remote that has never been fetched has no tracking refs, so that section is omitted entirely. Not using the network in previews is a design rule — see Network operations.
  • With no remotes registered at all it suggests git remote add and exits.

--siblings — fetching the repositories next door

Every other fuzgit command touches only the current repository. --siblings is the single exception. Given the flag, it scans the parent directory of the current worktree root, one level deep (never recursively) and offers the directories that contain a .git.

$ 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

The candidate list

  • A line is <directory name> <remote>/<current branch>, one pair per remote. A repository whose HEAD points at no branch reads <directory name> <remotes> detached HEAD instead. Columns are padded to the widest value, so the next column starts in the same place on every line.
  • The current repository starts selected (Tab deselects it); the others start unselected.
  • No remote registered and bare repositories cannot be fetched, so they are excluded — and the header says how many, never silently.
  • Linked worktrees of one repository are deduplicated into a single entry, so the same remote is not contacted twice. Symlinked directories are not followed.
  • When the current repository is the only target the finder does not open. The reason is printed and the fetch runs.
    $ gz fetch --siblings
    The current repository is the only target, so the selection was skipped
    [1/1] only
    1 succeeded / 0 failed
  • The preview is local information only: each remote’s URL, and each local branch’s tracking state from git for-each-ref refs/heads. It runs with --no-optional-locks, so it never takes a lock in somebody else’s repository.
    ── origin ──
    git@github.com:example/big-repo.git
    ── Branch tracking status ──
      develop → origin/develop [ahead 2]
    * main → origin/main [behind 4]
Colour

On a terminal, the lines fuzgit writes itself are coloured so they stand out from git’s own output: the [n/N] progress lines are cyan (a colour git does not use), and the closing summary is green, or red as soon as one target failed. In git’s update table the status code is coloured too — green for a new branch, yellow for a tag, red for a deletion, a forced update or a rejection — while a plain fast-forward, the ordinary result, is left alone. Only the status code is painted, never the reference names. Set NO_COLOR to turn this off; output to a pipe or a file is never coloured to begin with.

Why no git status in the preview

git status walks the whole working tree before printing anything, which can take seconds per preview in a large repository and would stall every cursor move. The ahead/behind you actually need to decide about a fetch is in the tracking state above. Anything else that would cost one git process per repository is kept out of the list for the same reason — the first paint stays fast.

The run

  • Each repository runs git fetch [--prune] --all, in parallel. Output is captured and written out as one block per repository when it finishes, headed by [<done>/<total>] <directory name>, so update tables never interleave.
  • How many run at once comes from git config fuzgit.fetchJobs — an integer of 1 or more, 4 when unset, and 1 for the fully serial behaviour. An invalid value stops the run before anything is fetched instead of quietly falling back. There is no CLI flag for it.
    git config fuzgit.fetchJobs 8   # or --global
  • --prune applies to every selected repository. The finder header says so.
  • git config fuzgit.notify true posts a desktop notification when the run finishes (see Completion notifications).
  • A failing repository does not stop the run. The rest still execute, and the summary at the end names the ones that failed. If even one fails the exit code is 1; git has already shown the reason, so fuzgit does not repeat it.
    $ gz fetch --siblings
    [1/3] mike
    [2/3] alpha
    [3/3] zulu
    fatal: '/path/to/does-not-exist.git' does not appear to be a git repository
    fatal: Could not read from remote repository.
    2 succeeded / 1 failed (failed: zulu)
    error: Fetching failed for some repositories
  • If launching git itself fails (git not installed, say) the run stops there — that is an environment problem, not a per-repository failure.
  • With nothing fetchable at all it stops with a dedicated error. So does a bare current repository, or one whose worktree root has no parent directory to scan. It never falls back to just the current repository, so the range you asked for and the place it contacts can never diverge.
    $ gz fetch --siblings
    error: There is no repository to fetch (a repository without a remote and a bare repository are not offered)
The parallel phase cannot ask you anything

No password, passphrase or host-key prompt can reach you while several fetches share the terminal. Any repository that fails there is run again, once, one at a time, with the terminal attached so you can answer. That second run is not a retry — it only changes how the command is run.

core.sshCommand costs you the speedup

The parallel phase overrides core.sshCommand with an environment variable, and an environment variable beats the config key (see man git-config). Every target then fails in parallel and falls through to the serial pass. The result is still correct; you simply do not get the speedup.

Common to both

  • git fetch runs with inherited stdio, so the list of updated refs, authentication prompts and progress output are git’s own.
  • Network and authentication failures are shown with git’s message as-is and exit non-zero (fuzgit adds no timeout and no retry).
gz fetch               # fetch 対象(リモート)を決めて git fetch する
gz fetch --prune       # リモートで削除されたブランチの追跡参照も掃除する
gz fetch --siblings    # 隣に並ぶリポジトリを選んでまとめて fetch する
オプション説明
-p, --pruneリモートで削除されたブランチの追跡参照を削除する(git fetch --prune)
-s, --siblings現在のリポジトリと同じ階層に並ぶリポジトリも対象に含める(複数選択)

このコマンドの主機能は「リモートを選ぶ」ことではなく 「fetch 対象の決定」です。 選択の余地がある場合にだけ fuzzy finder を起動します。

既定(現在のリポジトリ)

  • リモートが複数ある場合だけ finder を起動します。候補は登録済みの リモート名に、固定候補「すべてのリモート」を加えたものです。「すべてのリモート」を 選ぶと git fetch --all を実行します。
  • リモートが 1 つだけの場合は finder を起動せず、そのまま fetch します。 唯一のリモートと「すべてのリモート」の 2 択は、どちらを選んでも通信先が同じためです。 何に対して通信したのかが分かるよう、実行前に対象を 1 行だけ標準エラーへ示します (git fetch は更新が無ければ何も出力しないことがあるため)。
    $ gz fetch
    リモート `origin` から取得します(登録されているリモートが 1 つのため、選択を省略しました)
  • プレビューはローカル情報だけを表示します (「リモート URL」と「既知のリモート追跡ブランチ」の 2 セクション)。一度も fetch して いないリモートは追跡参照が無いため、そのセクションごと省略されます。 プレビューでネットワークを使わないのは設計上の原則です (ネットワーク操作について参照)。
  • リモートが 1 つも登録されていない場合は git remote add を促して終了します。

--siblings — 隣のリポジトリもまとめて取得

fuzgit の他のコマンドは現在のリポジトリだけを操作します。 --siblings はその唯一の例外で、フラグを明示したときに限り、 ワークツリー root の親ディレクトリ直下のみを走査し(再帰しません)、 .git を持つディレクトリを候補にします。

$ gz fetch --siblings
現在のリポジトリを選択済みにしています。Tab: 選択の切替 / Enter: 取得  |  除外 1 件(リモート未登録 / bare)
>> mike   origin/main
   alpha  origin/main
   zulu   origin/main

候補一覧

  • 候補行は ディレクトリ名 リモート/現在のブランチ で、リモートの数だけ 並びます。HEAD がブランチを指していないリポジトリは ディレクトリ名 リモート detached HEAD と表示します。各列は一覧中で 最も長い値に合わせて空白で埋めるため、右の列の開始位置はすべての行で揃います。
  • 現在のリポジトリは起動時から選択済みです (Tab で外せます)。他は未選択です。
  • リモート未登録のリポジトリと bare リポジトリは fetch できないため除外し、除外件数をヘッダーに示します(黙って消しません)。
  • 同じリポジトリの linked worktree が隣に並んでいる場合は1 件に重複排除します (同じリモートへ二重に通信しないため)。シンボリックリンクのディレクトリは辿りません。
  • 対象が現在のリポジトリ 1 件だけの場合は finder を起動せず、 理由を示してそのまま実行します。
    $ gz fetch --siblings
    対象が現在のリポジトリ 1 件のため、選択を省略しました
    [1/1] only
    成功 1 件 / 失敗 0 件
  • プレビューはローカル情報だけです(リモートごとの URL と、 git for-each-ref refs/heads によるローカルブランチごとの追跡状況)。 他人のリポジトリでロックを取らないよう --no-optional-locks を付けます。
    ── origin ──
    git@github.com:example/big-repo.git
    ── ブランチの追跡状況 ──
      develop → origin/develop [ahead 2]
    * main → origin/main [behind 4]
色

端末では、fuzgit 自身が出す行に色が付き、git の出力と見分けられます。 [n/N] の進捗行はシアン(git が使わない色)、末尾の集計行は 成功なら緑、1 件でも失敗すれば赤になります。git の更新表も状態コードだけ 着色し、新しいブランチは緑、タグは黄、削除・強制更新・拒否は赤、 通常の結果である早送りの更新は無色のままにします。色を乗せるのは 状態コードだけで、参照名には乗せません。 NO_COLOR を設定すると止められます。パイプやファイルへの 出力は元から着色されません。

プレビューに git status を出さない理由

git status は結果を出す前に作業ツリー全体を走査するため、 ファイル数の多いリポジトリではプレビュー 1 件に数秒掛かり、カーソルを 動かすたびに待たされます。fetch の判断に必要な ahead / behind は上記の 追跡状況で分かります。1 リポジトリあたり git の起動が必要な情報を候補一覧に 載せないのも同じ理由です(初期表示を速く保つため)。

実行

  • 各リポジトリで git fetch [--prune] --all を並列に 実行します。出力はキャプチャし、完了した順に [<完了件数>/<全体>] <ディレクトリ名> を頭に付けて ひとまとまりで書き出すため、複数リポジトリの更新表が混ざりません。
  • 同時実行数は git config fuzgit.fetchJobs で決まります (1 以上の整数。未設定なら 4、1 で完全な直列実行)。 不正な値のときは既定値へ暗黙に倒さず、1 件も fetch しないまま停止 します。CLI のフラグは設けていません。
    git config fuzgit.fetchJobs 8   # --global も可
  • --prune は選択したすべてのリポジトリに適用されます (その旨は finder のヘッダーに表示します)。
  • git config fuzgit.notify true を設定すると、 完了をデスクトップ通知で知らせます (完了通知参照)。
  • 途中で失敗しても中断せず残りを実行し、最後に成功・失敗件数と 失敗したリポジトリ名を集計します。1 件でも失敗すれば終了コード 1 です(失敗理由は git が表示済みのため再掲しません)。
    $ gz fetch --siblings
    [1/3] mike
    [2/3] alpha
    [3/3] zulu
    fatal: '/path/to/does-not-exist.git' does not appear to be a git repository
    fatal: Could not read from remote repository.
    成功 2 件 / 失敗 1 件(失敗: zulu)
    エラー: 一部のリポジトリで取得に失敗しました
  • git の起動そのものに失敗した場合(git 未インストール等)は その場で中断します。環境の問題であり、リポジトリごとの失敗ではないためです。
  • fetch できるリポジトリが 1 件も無い場合、現在のリポジトリが bare の場合、 ワークツリー root の親ディレクトリを取得できない場合は、いずれも専用エラーで 停止します。暗黙に現在のリポジトリだけへ倒すことはしません (指定した範囲と通信先が食い違わないようにするため)。
    $ gz fetch --siblings
    エラー: fetch できるリポジトリがありません(リモートが登録されていないリポジトリと bare リポジトリは対象になりません)
並列フェーズでは対話できません

複数の fetch が端末を共有するため、パスワード・passphrase・ホスト鍵の確認は 届きません。そこで失敗したリポジトリだけを、端末をつないだまま 1 件ずつ実行し直します。これはリトライではなく 実行方法の切り替えであり、1 回だけ行います。

core.sshCommand を設定すると高速化されません

並列フェーズは core.sshCommand を環境変数で上書きし、環境変数は config より優先されます(man git-config)。そのため対象がすべて 並列フェーズで失敗し、直列フェーズへ回ります。結果は正しく得られますが、 高速化の恩恵は受けられません。

共通

  • git fetch は継承 stdio で実行するため、更新された参照の一覧・認証プロンプト・進捗表示は git のものがそのまま出ます。
  • ネットワーク・認証の失敗は git のメッセージをそのまま表示して非ゼロ終了します(fuzgit 側でのタイムアウト・リトライは行いません)。

gz pull — bringing several branches up to date gz pull — 複数ブランチの一括取り込み network通信y/Nmulti-select複数選択 #

gz pull            # pick branches and fast-forward them onto their upstream
gz pull --rebase   # rebase the current branch onto its upstream
gz pull --merge    # merge the upstream into the current branch
OptionDescription
-r, --rebaseIntegrate the current branch by rebasing onto its upstream (rewrites history)
--mergeIntegrate the current branch by merging its upstream

Without a flag, the only thing you pick is which branches should follow their upstream — several local branches of the current repository. You never pick a remote × branch combination, because the destination is fixed to each branch’s own upstream. There is no --prune and no positional branch name. --rebase and --merge are a different shape entirely; see Two shapes below.

  • Candidates are local branches that have an upstream whose branch.<name>.remote is a registered remote name. Branches without an upstream, with an unregistered remote, or checked out in another worktree have no determinable destination, so they are excluded — and the number excluded is shown in the header (never dropped silently).
  • The current branch is put first, marked with a leading *  (as in gz branch), and starts selected (Tab deselects it). On a detached HEAD there is simply no current branch, and the other branches are offered as usual (unlike gz pull --rebase / --merge, this is not an error).
    $ 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
  • A candidate line is <mark><branch> → <short upstream name>. ahead / behind is deliberately not shown: at that moment fuzgit only knows the last fetch’s tracking refs, which is not how much is about to arrive, and a stale “not behind” would mislead. The real figure appears in git’s own update table at run time.
  • The preview is headed ── Commits not integrated yet (as of the last fetch) ── and shows the commits between the branch and its locally stored tracking ref (git log --oneline <branch>..<tracking ref>). The heading states that the information is as of the last fetch: the preview uses no network (see Network operations).
  • With only one candidate the finder does not open; the target and the reason are printed in one line and the run starts.
    $ gz pull
    Integrating `main` (there is only one candidate, so the selection was skipped)
    [1/1] main
    1 succeeded / 0 failed
  • With no candidates at all it stops and explains how to set an upstream (git push -u <remote> <branch> / git branch --set-upstream-to=<remote>/<branch>).
  • Execution starts with git fetch <remote> once per remote that the selected upstreams belong to (never once per branch). If one remote fails to fetch, every branch whose upstream lives on that remote is recorded as failed and skipped, while branches on other remotes continue — so nothing is ever integrated implicitly from a stale tracking ref.
  • Integration then happens one branch at a time, serially, in candidate order (never in parallel). Before each one, [<n>/<total>] <branch> is printed to stderr (same format as gz fetch --siblings), because git’s update tables and rejection reasons also go to stderr.
  • The currently checked out branch is integrated with git merge --ff-only <tracking ref> (the working tree and index have to be updated), while every other branch uses git fetch . <tracking ref>:refs/heads/<branch> (updating only the ref, never touching the working tree). The latter refspec deliberately omits the forcing +: with it, non-fast-forward updates would go through and commits that exist only on that branch would be lost.
  • A failing branch does not stop the run; the rest still execute and successes, failures and the failed branch names are summarised at the end. If even one fails the exit code is 1 (git already printed the reason, so fuzgit does not repeat it). Only when at least one branch could not fast-forward is a one-line pointer to the next step added.
    $ 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
    error: Integrating failed for some branches
    It only points at it — a rebase or merge is never started automatically. How history is integrated (rebase or merge) stays an explicit user choice.
  • If launching git itself fails (git not installed, for example) the run stops there, because that is an environment problem rather than a per-branch failure.
  • No [y/N] prompt in this shape. Multi-selecting and pressing Enter is already an explicit decision, and a fast-forward loses neither existing commits nor working tree changes — the same judgement as gz fetch --siblings. --rebase / --merge do confirm.
  • Run while a merge or rebase is in progress, it shows the recovery menu instead of the branch selection.
Why fast-forward only

“Get back to the state before the pull if something goes wrong” is met not by a rollback routine but by never running an operation that can break anything. A fast-forward does not recreate existing commits, and git refuses without changing anything when it cannot fast-forward, when the change would clash with the working tree, or when the branch is in use by another worktree. A failure therefore leaves the pre-pull state intact, and there is nothing to roll back.

The alternatives were rejected for the same reason: git merge --abort cannot always reconstruct the original state when there are uncommitted changes, and git reset --hard discards them outright.

Two shapes: fast-forward in bulk, or one branch your way

Aspectgz pullgz pull --rebase / --merge
TargetSeveral local branches that you pickThe current branch only (fixed to its upstream)
IntegrationFast-forward onlyRebase onto, or merge, the upstream
Selection UIMulti-select of branchesNone — there is nothing to pick
Confirmation promptNoYes

A branch that plain gz pull could not fast-forward is integrated by switching to it and running gz pull --rebase or gz pull --merge.

The reason --rebase / --merge cover only the current branch is mechanical: a branch that is not checked out can be moved along by copying a ref (git fetch . <tracking>:refs/heads/<branch>), which is fast-forward by definition. Rebasing or merging it would mean switching the working tree, or creating a temporary worktree — both of which put work you have in progress at risk.

Where gz sync went

These two flags used to be a separate command, gz sync. It was folded in here so that “bring changes in” is one command rather than two.

gz pull            # 取り込むブランチを選んで、upstream へ fast-forward で追随させる
gz pull --rebase   # 現在のブランチを upstream へ rebase して取り込む
gz pull --merge    # 現在のブランチへ upstream を merge して取り込む
オプション説明
-r, --rebase現在のブランチを upstream へ rebase して取り込む(履歴を書き換える)
--merge現在のブランチへ upstream を merge して取り込む

フラグ無しで選ばせるのは「どのブランチを upstream へ追随させるか」だけ です。対象は現在のリポジトリの複数のローカルブランチで、リモート × ブランチの 組み合わせは選ばせません(追随先は各ブランチの upstream に固定)。 --prune やブランチ名の位置引数はありません。 --rebase / --merge は形そのものが異なります — 下の2 つの形を参照してください。

  • 候補は upstream が設定され、その branch.<name>.remote が 登録済みのリモート名であるローカルブランチです。upstream が無いもの・ リモートが登録されていないもの・他の worktree でチェックアウト中のものは 取り込み先を決められないため候補から除外し、除外した件数をヘッダーに 示します(黙って消しません)。
  • 現在のブランチは一覧の先頭に置き、行頭を *  (gz branch と同じ)でマークして、起動時から選択済みにします (Tab で外せます)。detached HEAD の場合は現在のブランチが無いだけで、 他のブランチは通常どおり候補になります(gz pull --rebase / --merge と 違いエラーにはしません)。
    $ gz pull
    現在のブランチを選択済みにしています。Tab: 選択の切替 / Enter: fast-forward のみで取り込み  |  除外 2 件(upstream 未設定 / リモート未登録 / 他の worktree で使用中)
    >> * main       →  origin/main
         feature-a  →  origin/feature-a
  • 候補行は <マーク><ブランチ名> → <upstream の短縮名> です。 ahead / behind は意図的に表示しません。この時点で分かるのは 前回 fetch した追跡参照との差であり、これから取り込む本数とは一致せず、 古い「遅れていない」は誤解を招くためです。実際の本数は実行時に git 自身が出す 更新表で分かります。
  • プレビューは ── 未取り込みのコミット(前回の fetch 時点) ── の見出しを 付けて、ローカルに保存済みの追跡参照との差分コミット (git log --oneline <ブランチ>..<追跡参照>)を表示します。 それが前回の fetch 時点の情報であることを見出しで示しており、 プレビューではネットワークを使いません(ネットワーク操作について参照)。
  • 候補が 1 件だけの場合は finder を起動せず、対象と省略の理由を 1 行示してそのまま実行します。
    $ gz pull
    `main` を取り込みます(候補が 1 件のため、選択を省略しました)
    [1/1] main
    成功 1 件 / 失敗 0 件
  • 候補が 1 件も無い場合は、upstream の設定方法(git push -u <remote> <branch> / git branch --set-upstream-to=<remote>/<branch>)を案内して停止します。
  • 実行はまず、選択したブランチの upstream が属するリモートごとに 1 回だけ git fetch <remote> します(ブランチごとに通信しません)。 あるリモートの取得に失敗した場合は、そのリモートを upstream に持つブランチをすべて 失敗として記録して飛ばし、他のリモートのブランチは続行します(古い追跡参照のまま 暗黙に取り込まないためです)。
  • 取り込みは候補一覧の順序で 1 ブランチずつ直列に実行します (並列にしません)。実行前に [<何番目>/<全体>] <ブランチ名> を 標準エラーへ表示します(gz fetch --siblings と同形式)。git 自身の更新表や 拒否の理由も標準エラーに出るため、この 1 行が無いとどのブランチの出力なのか 読み取れなくなります。
  • 現在チェックアウト中のブランチは git merge --ff-only <追跡参照>(作業ツリーと index を 更新する必要があるため)、それ以外のブランチは git fetch . <追跡参照>:refs/heads/<ブランチ> (参照だけを更新し作業ツリーに触れない)で取り込みます。後者の refspec に強制更新の + は付けません。付けると非 fast-forward の更新が通り、 そのブランチにしか無いコミットを失わせるためです。
  • 途中のブランチが失敗しても中断せず残りを実行し、最後に成功件数・ 失敗件数と失敗したブランチ名を集計表示します。1 件でも失敗した場合は 終了コード 1 で終わります(失敗理由は git のメッセージがその場で 表示済みのため、fuzgit は再掲しません)。fast-forward できなかったブランチが 1 件以上あるときだけ、次の手段を 1 行案内します。
    $ gz pull
    [1/4] main
    [2/4] alpha
    [3/4] diverged
    [4/4] zeta
    成功 3 件 / 失敗 1 件(失敗: diverged)
    fast-forward できなかったブランチは、そのブランチへ切り替えてから `gz pull --rebase` または `gz pull --merge` で取り込めます
    エラー: 一部のブランチで取り込みに失敗しました
    案内するだけで、rebase や merge を自動で始めることはありません。 履歴の統合方法(rebase か merge か)はユーザーの明示指定に限ります。
  • git の起動そのものに失敗した場合(git 未インストール等)は、 その場で中断します。環境の問題でありブランチごとの失敗ではないためです。
  • この形では [y/N] の確認プロンプトはありません。 複数選択して Enter を押すこと自体が明示的な決定であり、fast-forward は 既存のコミットも作業ツリーの変更も失わせないためです (gz fetch --siblings と同じ判断)。--rebase / --merge では確認します。
  • merge / rebase が進行中の状態で実行した場合は、ブランチ選択ではなく 復帰メニューが表示されます。
fast-forward のみに固定する理由

「何かあれば pull 前の状態に戻す」ことを、ロールバック処理ではなく 壊し得る操作を行わないことで満たすためです。fast-forward は 既存のコミットを作り直さず、fast-forward できない場合・作業ツリーの変更と 衝突する場合・他の worktree で使用中の場合は、いずれも git が 何も変更せずに拒否します。そのため失敗しても取り込み前の状態が そのまま残り、巻き戻す対象がありません。

代替案を採らなかった理由も同じです。git merge --abort は未コミットの 変更があると元の状態を再構成できないことがあり、git reset --hard は 未コミットの変更を破棄します。

2 つの形 — まとめて fast-forward か、1 本を方式を選んで取り込むか

観点gz pullgz pull --rebase / --merge
対象選んだ複数のローカルブランチ現在のブランチのみ(upstream に固定)
取り込み方式fast-forward のみupstream へ rebase、または merge
選択 UIブランチの複数選択無し — 選ぶものが無いため
確認プロンプト無しあり

フラグ無しの gz pull で fast-forward できなかったブランチは、 そのブランチへ切り替えてから gz pull --rebase または gz pull --merge で取り込みます。

--rebase / --merge が現在のブランチだけを対象に するのは機構上の理由です。チェックアウトしていないブランチは参照のコピー (git fetch . <tracking>:refs/heads/<branch>)でしか 追随させられず、これは定義上 fast-forward です。rebase や merge を行うには 作業ツリーを切り替えるか一時 worktree を作る必要があり、いずれも進行中の作業を 危険に晒します。

gz sync の行き先

この 2 つのフラグは、以前は gz sync という独立したコマンドでした。 「取り込む」操作を 2 つのコマンドに分けると使い分けが読み取れないため、 こちらへ畳んで 1 つにまとめています。

gz worktree — listing and managing worktrees gz worktree — worktree の一覧・管理 y/N #

gz worktree                  # pick from the list and print the path to stdout
cd "$(gz worktree)"          # shell integration (move to the picked worktree)
gz worktree add <name>       # pick a branch and create a new worktree next to the repository
gz worktree add -a <name>    # offer remote-tracking branches as well
gz worktree add -b <branch> <name>   # create a new branch for it, picking its starting point
gz worktree remove           # pick a worktree and remove it (with confirmation)
gz worktree remove --force   # same, discarding modified and untracked files inside it (with a warning)
gz worktree prune            # tidy up bookkeeping for worktrees whose directory is gone (with confirmation)

No arguments (list, then print the path)

  • A candidate line is <path> main|linked <branch|detached|bare>[ locked][ prunable].
  • The preview shows the commit log of the highlighted worktree’s HEAD (bare worktrees and worktrees without a single commit get no preview).
  • On confirmation only the path of the picked worktree goes to stdout; messages go to stderr.
Known behaviour

Redirecting stdout to a file catches skim’s drawing escapes in that file too — gz worktree > out.txt and gz log > log.txt behave the same. Pipes and command substitution are fine, because the drawing goes to the terminal, so cd "$(gz worktree)" works as expected.

gz worktree add <name>

ArgumentDescription
<NAME>Name of the worktree to create (required positional argument). A name, not a path — separators are refused
-a, --allInclude remote-tracking branches in the candidates, the same spelling as gz branch -a. Cannot be combined with -b
-b <BRANCH>Create a new branch with this name and put that in the worktree. No long spelling — git worktree add -b has none either
  • What you pick in the fuzzy finder is the branch to check out. Candidates are limited to branches not in use by another worktree (git cannot check out the same branch in two worktrees at once).
  • -a adds remote-tracking branches, exactly as gz branch -a does. Picking origin/feature hands git the short name (feature) and git’s DWIM creates the local branch that tracks it. The “in use elsewhere” filter is applied to that short name, so origin/feature disappears from the list while a local feature is checked out in another worktree — picking it could only ever fail. A local branch and its remote counterpart are both listed, as in gz branch -a.
  • The worktree is always created next to the repository root, whatever directory you run the command from. Given a repository at product/infrastructure/, gz worktree add sample creates product/sample/. A worktree holds the whole tree below .git, so placing it beside the repository reads more naturally than nesting it inside — and a worktree inside the repository shows up as an untracked embedded repository that you then have to gitignore.
  • Because that is not where you ran the command, the resulting path is printed to stderr once the worktree exists. (Not stdout: git worktree add writes HEAD is now at ... there itself, so stdout could never carry the path alone.)
  • The argument is a name. Anything containing a path separator (../sample, a/b, an absolute path), as well as . and .., is refused with an explanation rather than being reinterpreted.
  • It runs git worktree add -- <resolved path> <branch>. Names are never suggested automatically.
  • For a name starting with -, write gz worktree add -- -dashy (that -- is clap’s; fuzgit adds its own -- when calling git).
.claude/ is copied across

git worktree add only writes out tracked files, so a gitignored .claude/ would be missing in the new worktree. fuzgit copies it over from the worktree you ran the command in. Files that already exist on the other side are left alone, symlinks are not followed, and a failure is reported on stderr without failing the command — the worktree itself was created. It is a copy, not a link, so the two sides can diverge.

-b <branch> — when there is no branch left to pick

With one local branch (main) checked out by the main worktree, the candidate list is empty and gz worktree add is a dead end. -b is the way out: it creates a new branch and puts that in the worktree.

-b changes what you pick

Without -b you pick an existing local branch. With it you pick the starting point of the new branch — local branches, remote-tracking branches and tags, exactly the candidates gz branch create offers. The finder header says which one you are looking at, because the rows alone do not tell you.

  • The starting point is not fixed to HEAD. Cutting a work branch from main is the common case, but the main worktree is often on something else — HEAD would give you the wrong parent. main / master are sorted first, so Enter alone usually lands where you meant.
  • A name already taken by a local branch stops the command before the finder opens, and says that dropping -b would put that branch in the worktree instead.
  • The upstream is left to git’s own default: it is set only when the starting point is a remote-tracking branch, matching what gz branch create produces.
  • -b and --no-install are independent. The dependency install and the .claude/ copy run for -b too.

Installing dependencies

After the worktree exists, fuzgit looks for lockfiles and installs the dependencies. What it searches is the tracked files — it asks git ls-files rather than walking the filesystem, so node_modules/ and target/ are never in the way and there is no depth limit.

  • With more than one directory to install into, you pick them. A line is <path> <the commands that would run>, and the preview is the lockfile itself. Tab selects several; they run in order with [<n>/<total>] in front.
  • The directory you ran the command in starts selected, so pressing Enter installs exactly what it would have installed before this was a choice.
  • With only one directory the finder does not open — a single-project repository behaves as it always did.
  • Cargo and Go are not offered. Their dependencies live in a global cache and are fetched at build time, so a fresh worktree has nothing to install.
  • --no-install skips all of this, finder included.

gz worktree remove

  • Candidates are linked worktrees only — the main worktree is not offered (git does not allow removing it).
  • Locked worktrees, and worktrees with modified or untracked files when --force is not given, fail with git’s message as-is and a non-zero exit code.
  • -f / --force passes --force to git worktree remove (once — a locked worktree is still refused), and the confirmation prompt then warns that modified and untracked files inside the worktree are discarded as well.
Deletes a directory

A confirmation prompt ([y/N]) comes first. Without --force, a worktree holding modified or untracked files is refused by git; with it, those files are discarded along with the directory, so the prompt says so explicitly.

gz worktree prune

  • It first runs git worktree prune --dry-run --verbose and puts the worktrees that would be pruned, with the reason, into the confirmation prompt. git worktree prune runs only if you approve.
  • With nothing to prune it reports so and exits with code 0.
gz worktree                  # 一覧から選んでパスを標準出力へ出す
cd "$(gz worktree)"          # シェル連携(選んだ worktree へ移動する)
gz worktree add <name>       # ブランチを選び、リポジトリの兄弟として worktree を作る
gz worktree add -a <name>    # リモート追跡ブランチも候補に含める
gz worktree add -b <branch> <name>   # 新しいブランチを作って入れる(作成元を選ぶ)
gz worktree remove           # worktree を選んで削除する(確認あり)
gz worktree remove --force   # 変更済み・未追跡のファイルごと削除する(警告つきの確認あり)
gz worktree prune            # 実体を失った worktree の管理情報を整理する(確認あり)

引数なし(一覧 → パス出力)

  • 候補行は <パス> main|linked <ブランチ|detached|bare>[ locked][ prunable] です。
  • プレビューには選択中 worktree の HEAD のコミットログを表示します(bare とコミットが 1 件も無い worktree はプレビューを出しません)。
  • 決定すると選んだ worktree のパスだけを標準出力へ出します。メッセージ類は標準エラーです。
既知の挙動

標準出力をファイルへリダイレクトすると、skim の描画エスケープも そのファイルへ混ざります(gz worktree > out.txt / gz log > log.txt のいずれも同じ)。パイプ・コマンド置換では 描画が端末側へ出るため、cd "$(gz worktree)" は期待どおり動作します。

gz worktree add <name>

引数説明
<NAME>作成する worktree の名前(必須の位置引数)。パスではなく名前で、区切り文字は受け付けない
-a, --allリモート追跡ブランチも候補に含める(gz branch -a と同じ綴り)。-b とは併用できない
-b <BRANCH>この名前で新しいブランチを作り、それを worktree に入れる。長綴りは無い(git worktree add -b にも無いため)
  • fuzzy finder で選ぶのはチェックアウトするブランチです。候補は 他の worktree で使用中でないブランチに限ります (git は同じブランチを複数の worktree で同時にチェックアウトできないため)。
  • -a でリモート追跡ブランチも候補に含めます(gz branch -a とまったく同じ規則)。origin/feature を選ぶと git へは短縮名 (feature)を渡し、git の DWIM が追跡するローカルブランチを作ります。 「使用中」の判定はその短縮名に対して行うため、ローカルの feature が別の worktree でチェックアウト中なら origin/feature も候補から消えます(選んでも必ず失敗するため)。 ローカルブランチとそのリモート側は gz branch -a と同じく両方並びます。
  • worktree は必ずリポジトリルートの兄弟に作成します。どのディレクトリで コマンドを叩いても変わりません。リポジトリが product/infrastructure/ に あるとき、gz worktree add sample は product/sample/ を作ります。 worktree の中身は .git のある階層まるごとなので、リポジトリと並べたほうが 素直に読めるためです(内側に作ると、本体からは未追跡の埋め込みリポジトリとして見え、 .gitignore の手当てが必要になります)。
  • 叩いた場所とは違うところに作られるため、作成後にそのパスを標準エラーへ表示します。 (標準出力は使いません。git worktree add 自身が HEAD is now at ... をそちらへ書くため、パスだけを取り出せる状態には ならないからです。)
  • 引数は名前です。パス区切りを含むもの(../sample / a/b / 絶対パス)と . / .. は、読み替えずに理由を示して拒否します。
  • 実行するのは git worktree add -- <解決後のパス> <branch> です。名前の自動提案は行いません。
  • - で始まる名前を指定する場合は gz worktree add -- -dashy の ように書きます(clap 側の -- であり、fuzgit は git へ渡す際にも別途 -- で保護します)。
.claude/ を複写します

git worktree add は追跡ファイルしか書き出さないため、gitignore された .claude/ は新しい worktree に現れません。そこで、コマンドを実行した worktree の .claude/ を複写します。複写先に既にある ファイルは上書きせず残し、シンボリックリンクは辿りません。 失敗しても標準エラーへ知らせるだけでコマンドは失敗しません(worktree の作成自体は 成功しているため)。リンクではなく複写なので、両者は独立して変化します。

-b <branch> — 選べるブランチが無いとき

ローカルブランチが main 1 本で、それを本体が使用中——という ごく普通の状態では候補がゼロになり、gz worktree add はそこで行き止まりになります。-b はその抜け道で、新しいブランチを 作ってそれを worktree に入れます。

-b は「選ぶもの」を変えます

-b 無しで選ぶのは既存のローカルブランチ、 有りで選ぶのは新しいブランチの作成元です(ローカル・ リモート追跡ブランチ・タグ。gz branch create とまったく同じ候補)。候補行だけでは区別が付かないため、どちらを見ているかは finder のヘッダーが示します。

  • 作成元は HEAD 固定ではありません。main から 作業ブランチを切るのが典型ですが、本体が別のブランチをチェックアウト中である ことは珍しくなく、HEAD 固定では望まない親から切ってしまいます。 main / master を先頭へ寄せているため、 多くの場合は Enter 1 回で意図した作成元になります。
  • 既存のローカルブランチと同名の場合は、finder を開く前に停止します。 -b を外せばそのブランチを worktree に入れられることも案内します。
  • upstream は git の既定に委ねます。作成元がリモート追跡ブランチのときだけ 設定され、gz branch create の結果と一致します。
  • -b と --no-install は独立です。 依存インストールと .claude/ の複写は -b でも走ります。

依存インストール

worktree ができたあと、fuzgit は lockfile を探して依存をインストールします。 探すのは追跡ファイルです — ファイルシステムを走査せず git ls-files に引かせるため、node_modules/ や target/ を踏むことがなく、深さの制限もありません。

  • 対象が複数あるときは選べます。候補行は <パス> <実行されるコマンド> で、プレビューは lockfile そのものです。Tab で複数選択でき、 [<n>/<全体>] を頭に付けて順に実行します。
  • コマンドを叩いたディレクトリは選択済みで開くため、 そのまま Enter を押せば、選べるようになる前と同じものが入ります。
  • 対象が 1 件のときは finder を開きません — 単一プロジェクトのリポジトリでは従来どおりの挙動です。
  • Cargo と Go は候補になりません。依存がグローバルキャッシュに置かれ、 ビルド時に取得されるため、新しい worktree に入れるものがありません。
  • --no-install を付けると、finder を含めて一切行いません。

gz worktree remove

  • 候補は linked worktree のみで、main worktree は候補に含めません(git が削除を許可しないため)。
  • locked な worktree、および --force 無しでの変更済み・未追跡ファイルを含む worktree は、git のエラーをそのまま表示して非ゼロ終了します。
  • -f / --force は git worktree remove に --force を(1 回だけ — locked な worktree は引き続き拒否されます)渡し、確認プロンプトに「worktree 内の変更済み・未追跡のファイルも破棄される」旨の警告を追記します。
ディレクトリを削除します

実行前に確認プロンプト([y/N])を表示します。--force 無しでは変更済み・未追跡のファイルを抱えた worktree を git が拒否します。 付けた場合はそれらのファイルごとディレクトリが消えるため、プロンプトが その旨を明示します。

gz worktree prune

  • 先に git worktree prune --dry-run --verbose を実行し、整理される worktree と 理由を確認プロンプトに提示します。承認された場合のみ git worktree prune を 実行します。
  • 対象が 1 件も無い場合は「整理する worktree はありません」と表示して終了コード 0 で終わります。

gz pr — picking a pull request to check out gz pr — checkout する PR の選択 network通信 #

gz pr                            # pick an open pull request and check it out
gz pr --checks                   # also show the review decision and the CI status (slower)
gz pr --action                   # pick a pull request, then pick what to do with it
gz pr --worktree review-142      # check it out into a new worktree next to the repository
gz pr --worktree review-142 --no-install   # …without installing dependencies
OptionDescription
--checksAlso fetch the review decision and the CI status (noticeably slower)
--actionAfter picking, show a menu of what to do (cannot be combined with --worktree)
--worktree <NAME>Check the pull request out into a new worktree with this name
--no-installDo not install dependencies after creating the worktree
gh is not a required dependency

gz pr is the only command that needs the GitHub CLI. Without it, only this command stops, with a message pointing at the install page; every other command keeps working. fuzgit still assumes nothing but git.

  • Candidates are the open pull requests of the current repository. A line is #<number> <author> <title>, with a [draft] marker in its own column so the columns stay aligned whether or not a pull request is a draft. The head branch name is deliberately not on the line — it is often long enough to push the title off the screen, and the title is what you scan. It appears at the right edge of the preview’s summary instead.
  • On confirmation it runs gh pr checkout <number>. What you end up with is ordinary local git state — a branch, its tracking config, and a checkout.
  • --action opens a second menu: check the pull request out, show it (gh pr view), show the diff (gh pr diff), print the number, and print the URL. Only the last two write to stdout, so $(…) stays usable.
  • Everything gh prints comes straight from gh. fuzgit never repeats it and never guesses at a cause.

Why the preview never waits

Every other fuzgit command builds its candidate list and previews from local data only, because a preview is regenerated on every cursor move and a network round trip there would block the display (see Network operations). gz pr is the single exception — and it breaks the rule exactly once.

The candidate list is fetched with the pull request body riding along in the same call. By the time the finder opens, every preview is already in memory, so moving the cursor is a local string draw. Measured against a repository with 30 open pull requests, adding the body costs about 0.2 seconds.

--checks is the slow one

reviewDecision and statusCheckRollup are expensive on GitHub's side: the default fetch takes about 1.1 seconds where --checks takes roughly three times that. They are opt-in for that reason, and the wait is the same whether or not you end up picking anything.

--worktree <name> — reviewing without disturbing your tree

  • The argument is a name, not a path. The worktree is created next to the repository root, exactly like gz worktree add. Anything containing a path separator is refused.
  • Afterwards fuzgit does what gz worktree add does: copies your gitignored .claude/ across (FR-33) and installs dependencies from the lockfile it finds. --no-install skips only the install; the .claude/ copy still happens.
  • A name already taken by another worktree stops the command before the finder opens — you are never asked to pick and then told it will not work.
gh reuses an existing worktree silently

Run bare, gh pr checkout --worktree accepts a path that already holds a worktree and simply checks out into it, reporting success. That is why fuzgit refuses the name up front rather than passing it through: “a new worktree was created” and “an existing one was reused” look the same otherwise.

gh speaks English only

fuzgit’s own messages switch with --lang, but gh has no translations, so its lines stay English even with ja. This is the same limitation as git’s own output (see Language).

gz pr                            # open な PR を選んで checkout する
gz pr --checks                   # レビュー判定と CI の状況も表示する(遅くなる)
gz pr --action                   # PR を選んだあと、続けて行う操作も選ぶ
gz pr --worktree review-142      # リポジトリの隣に新しい worktree を作って checkout する
gz pr --worktree review-142 --no-install   # …依存インストールは行わない
オプション説明
--checksレビュー判定と CI の状況も取得する(体感で遅くなる)
--action選んだあと、続けて行う操作をメニューから選ぶ(--worktree とは併用不可)
--worktree <NAME>この名前の新しい worktree へ checkout する
--no-installworktree 作成後に依存インストールを行わない
gh は必須依存ではありません

GitHub CLI を必要とするのは gz pr だけです。無い場合はこのコマンドだけが停止し、 導入先を案内します。他のコマンドはそのまま動作します。fuzgit が前提とする 外部コマンドは引き続き git のみです。

  • 候補は現在のリポジトリの open な PR です。候補行は #<番号> <作者> <タイトル> で、 [draft] の印は独立した列に置くため、draft の有無で桁がずれません。 head ブランチ名は意図的に載せていません — 長くなりがちでタイトルを画面外へ押し出してしまう一方、目で追うのは タイトルだからです。ブランチ名はプレビュー枠の右端に出ます。
  • 決定すると gh pr checkout <番号> を実行します。得られるのは ローカルの git 状態(ブランチ・追跡設定・チェックアウト)そのものです。
  • --action を付けると 2 つ目のメニューが出ます — checkout する・詳細を表示する(gh pr view)・ 差分を表示する(gh pr diff)・番号を出力する・ URL を出力する。標準出力へ書くのは後ろの 2 つだけなので、 $(…) はそのまま使えます。
  • gh の出力はそのまま流します。fuzgit は再掲もしませんし、 原因を推測することもしません。

プレビューが待たされない理由

fuzgit の他のコマンドは、候補一覧もプレビューもローカルの情報だけで作ります。 プレビューはカーソル移動のたびに作り直されるため、そこで ネットワーク往復が起きると描画が止まるからです (ネットワーク操作について参照)。 gz pr はその唯一の例外ですが、破るのは候補生成の 1 回だけです。

候補の取得では、PR の本文を同じ 1 回に相乗りさせています。 finder が開いた時点で全プレビューが手元にあるため、カーソル移動はローカルの 文字列描画で済みます。open な PR が 30 件のリポジトリでの実測で、本文の追加分は 約 0.2 秒です。

遅いのは --checks です

reviewDecision と statusCheckRollup は GitHub 側で 高くつきます。既定の取得が約 1.1 秒なのに対し、 --checks はおよそ 3 倍を要します。オプトインにしているのは このためで、結局何も選ばなかった場合も同じだけ待つことになります。

--worktree <name> — 手元を汚さずにレビューする

  • 引数はパスではなく名前です。作成先は gz worktree add と同じくリポジトリルートの 兄弟に固定で、パス区切りを含むものは拒否します。
  • 作成後は gz worktree add と同じことをします — gitignore された .claude/ を複写し(FR-33)、 見つけた lockfile から依存をインストールします。--no-install が 止めるのはインストールだけで、.claude/ の複写は行われます。
  • 他の worktree が既に使っている名前は、finder を開く前に停止します。 選ばせたあとで「使えません」と告げることはありません。
gh は既存の worktree を黙って再利用します

素の gh pr checkout --worktree は、既に worktree があるパスを 指定してもそこへチェックアウトして成功を報告します。fuzgit が名前を事前に 拒否するのはこのためです — そうしないと「新しく作られた」と 「既存のものが使われた」が見分けられません。

gh は英語だけを話します

fuzgit 自身の文言は --lang で切り替わりますが、gh には 翻訳がないため、ja を選んでも gh の行は英語のままです。 git 本体の出力と同じ制約です(表示言語参照)。

Network operationsネットワーク操作について #

  • The only commands that use the network are gz fetch, gz pull and gz pr. Every other command works purely from local repository data (pushing is left to plain git push). gz pr is also the only one that runs something other than git — it needs the gh CLI, which is not a required dependency.
  • Previews never use the network, and candidate generation does so in exactly one place. A preview is regenerated every time the highlighted entry changes, so a network round trip there (latency, timeouts, authentication prompts) would block the display — that rule has no exceptions. Candidate generation has one: gz pr lists pull requests from GitHub, and it fetches their bodies in the same call so its previews stay a local draw. Accordingly the gz fetch preview is limited to local data (git remote get-url / git for-each-ref refs/remotes/<remote>) and never runs git fetch --dry-run inside a preview; the list of updated refs is printed by git fetch itself when it runs. The gz pull preview (commits not yet integrated) likewise comes from reading locally stored tracking refs and is therefore as of the last fetch.
  • The only thing gz pull sends over the network is the git fetch <remote> per remote, run after the targets are fixed. Branches sharing an upstream remote are handled together, so there is one round trip per remote and never one per branch. Updating a branch that is not checked out (git fetch . <tracking ref>:refs/heads/<branch>) fetches from the repository itself and never goes out to the network.
  • The ahead/behind in gz status and the right-hand side of gz diff --upstream both come from reading local remote-tracking refs. Run gz fetch first if you want to compare against the latest state.
  • Authentication is left to git. git fetch runs with inherited stdio, so credential entry, credential helpers and progress output are all git’s own. fuzgit performs no timeouts, no retries and no direct socket handling whatsoever.
  • gz fetch --siblings runs its repositories in parallel; gz pull stays serial. The repositories are independent and the wait is network round trips, so they parallelise. gz pull does not: its per-remote fetch is effectively one call, and integrating branches writes to a single repository where the index and ref locks would collide.
  • Without --siblings, neighbouring repositories are not even offered as candidates. So that a mere change of directory cannot fire traffic at many repositories, the scan origin is always derived from the current repository and can never be given as a path.
  • Ctrl-C during a run is not handled specially: the SIGINT delivered to the foreground process group terminates fuzgit with it, and targets not yet started never run.
How the parallel phase is kept safe

It captures each repository’s output so update tables cannot interleave, and it forbids interaction — GIT_TERMINAL_PROMPT=0, GIT_ASKPASS="", ssh -o BatchMode=yes -o ControlMaster=no — so no prompt can appear with no way to tell which repository it came from. Whatever fails there is run again, one at a time, with the terminal attached.

  • ネットワークを使うのは gz fetch・ gz pull・gz pr だけです。ほかのコマンドはすべてローカルのリポジトリ情報だけで 動作します(push は素の git push に委ねます)。 gz pr は git 以外の外部コマンドを起動する 唯一のコマンドでもあります — gh CLI を使いますが、 これは必須依存ではありません。
  • プレビューはネットワークを使いません。候補生成も 1 か所を除いて 使いません。プレビューは選択項目が変わるたびに生成されるため、 そこでネットワーク往復(遅延・タイムアウト・認証プロンプト)が発生すると描画が ブロックされます — こちらに例外はありません。候補生成には 1 つだけ例外が あり、gz pr は GitHub から PR を一覧します。 ただし本文を同じ 1 回で取り切るため、プレビューは ローカルの描画に閉じます。したがって gz fetch のプレビューは ローカル情報(git remote get-url / git for-each-ref refs/remotes/<remote>)に限定しており、 git fetch --dry-run をプレビュー内で実行することはしません。 更新される参照の一覧は git fetch 自身が実行時に表示します。 gz pull のプレビュー(未取り込みのコミット)も同様に、ローカルに 保存済みの追跡参照を読んだ結果であり、前回の fetch 時点の情報です。
  • gz pull が通信するのは、対象を確定したあとに実行する リモートごとの git fetch <remote> だけです。 同じリモートを upstream に持つブランチをまとめて扱うため、通信は 1 リモートにつき 1 回で、ブランチごとには通信しません。チェックアウトしていないブランチの更新 (git fetch . <追跡参照>:refs/heads/<ブランチ>)は取得元が 自分自身のリポジトリであり、ネットワークへは出ません。
  • gz status の ahead / behind と gz diff --upstream の比較先は、いずれもローカルのリモート追跡参照を 読んだ結果です。最新の状態と比べたい場合は先に gz fetch を実行してください。
  • 認証は git に委ねます。git fetch は継承 stdio で 実行するため、認証情報の入力・credential helper・進捗表示はすべて git のものが そのまま動きます。fuzgit 側でのタイムアウト・リトライ・ ソケットの直接操作は一切行いません。
  • gz fetch --siblings は並列、gz pull は直列 です。前者は対象が互いに独立した別リポジトリで、待ちの実体がネットワークの 往復であるため並列化できます。後者はリモートごとの fetch が実質 1 件であり、 ブランチの取り込みは同一リポジトリへの書き込みで index や参照の lock が 競合するため直列のままです。
  • --siblings を指定しない限り、隣のリポジトリは候補にすら なりません。ディレクトリを移動しただけで多数のリポジトリへ通信が飛ぶことを 防ぐため、探索の起点は常に現在のリポジトリから導出し、パスでは指定できません。
  • 実行中の Ctrl-C は独自に処理せず、前面プロセスグループへ届いた SIGINT で fuzgit ごと終了します(まだ着手していない対象は実行されません)。
並列フェーズの安全策

リポジトリごとに出力をキャプチャして更新表が混ざらないようにし、さらに 対話を禁止します(GIT_TERMINAL_PROMPT=0・ GIT_ASKPASS=""・ssh -o BatchMode=yes -o ControlMaster=no)。 どのリポジトリのものか分からないプロンプトが出ないようにするためです。 そこで失敗した対象は、端末をつないだまま 1 件ずつ実行し直します。

Completion notifications完了通知 #

  • gz fetch --siblings and gz pull can post a desktop notification when they finish, which helps when you have stepped away from a long run. It is off unless you turn it on:
    git config --global fuzgit.notify true
  • Nothing is posted for runs shorter than ten seconds — below that you are still looking at the terminal. The threshold is fixed; there is no setting for it.
  • The body is the count only (3 succeeded / 1 failed). No repository, branch or path name is included: on macOS the text is handed to osascript, which interprets its argument as AppleScript, so user-derived strings are kept out of it by construction.
  • Whether a banner actually appears depends on your environment. macOS uses osascript and needs notifications allowed for your terminal application; Linux uses notify-send, which may not be installed. fuzgit never treats that as a failure — the exit code is unchanged, and the reason is only written to the debug log (FUZGIT_DEBUG=1).
  • The tally is always written to stderr whether or not a notification appears, so the notification is a convenience and never the only way you learn the result.
  • gz fetch --siblings と gz pull は、完了時に デスクトップ通知を出せます。長い実行の間に席を外しているときに役立ちます。 明示的に有効化しない限り出ません。
    git config --global fuzgit.notify true
  • 10 秒未満で終わった実行では通知しません。それより短ければ ユーザーはまだ端末を見ているためです。閾値は固定で、設定項目は設けていません。
  • 本文は件数だけです(成功 3 件 / 失敗 1 件)。 リポジトリ名・ブランチ名・パスは含めません。macOS では本文を osascript へ渡しますが、これは引数を AppleScript として解釈するため、 ユーザー由来の文字列を構造的に混ぜないようにしています。
  • 実際にバナーが表示されるかは環境によります。macOS は osascript を 使うため端末アプリへの通知許可が必要で、Linux は notify-send を 使うため未導入だと出ません。fuzgit はこれを失敗として扱いません。 終了コードは変わらず、理由はデバッグログ(FUZGIT_DEBUG=1)にだけ出ます。
  • 通知が出るかどうかに関わらず集計は必ず標準エラーへ出します。 通知はあくまで補助であり、結果を知る唯一の経路にはなりません。

Key bindingsキー操作 #

The fuzzy finder uses skim’s default key bindings as they are.

KeyAction
TypingFilter incrementally
↑ / ↓, Ctrl-p / Ctrl-n, Ctrl-k / Ctrl-jMove between candidates
Tab / Shift-TabToggle the selection of a candidate (multi-select mode only)
EnterConfirm
Shift-↑ / Shift-↓Scroll the preview
Esc / Ctrl-CAbort
  • Multi-select is available in cherry-pick / restore / add / stash push / commit / revert / status (file selection) / diff (file selection) / branch delete / branch cleanup / fetch --siblings / pull, and in the “inspect the conflicted files and mark them resolved” entry of the recovery menu. Pressing Enter without selecting anything with Tab targets the candidate under the cursor.
  • Entries that start out selected: gz commit (staged files), gz branch cleanup (every merged branch), gz fetch --siblings (the current repository) and gz pull (the current branch). All of them can be deselected with Tab.
  • The selection is always reordered to follow the candidate list before it is handed to git (the order skim returns is the order you picked, which is not necessarily the list order). Commands where the order matters each get their own direction: gz cherry-pick oldest first, gz revert newest first.
  • Aborting with Esc / Ctrl-C performs no git operation at all and exits with code 130.

fuzzy finder は skim の既定キーバインドをそのまま使います。

キー動作
文字入力インクリメンタルに絞り込む
↑ / ↓、Ctrl-p / Ctrl-n、Ctrl-k / Ctrl-j候補を移動する
Tab / Shift-Tab候補の選択を切り替える(複数選択モードのみ)
Enter決定する
Shift-↑ / Shift-↓プレビューをスクロールする
Esc / Ctrl-C中断する
  • 複数選択に対応するのは cherry-pick / restore / add / stash push / commit / revert / status(ファイル選択)/ diff(ファイル選択)/ branch delete / branch cleanup / fetch --siblings / pull、および復帰メニューの 「コンフリクトファイルを確認して解決済みにする」です。Tab で 1 件も 選ばずに Enter を押した場合は、カーソル位置の候補が対象になります。
  • 起動時から選択済みになるのは gz commit(ステージ済みのファイル)、 gz branch cleanup(merged なブランチ全件)、 gz fetch --siblings(現在のリポジトリ)、gz pull (現在のブランチ)です。いずれも Tab で外せます。
  • 選択結果は常に候補一覧を基準に並べ直してから git へ渡します (skim が返す順序は選んだ順であり、候補の並び順とは限らないため)。 gz cherry-pick は古い順、gz revert は新しい順というように、 順序が意味を持つコマンドはそれぞれの向きへ揃えます。
  • Esc / Ctrl-C で中断した場合、git 操作は一切実行せず 終了コード 130 で終了します。

Exit codes終了コード #

CodeMeaning
0 Success (including gz status with no changes, gz diff with no differences, gz pull --rebase with behind 0 and gz worktree prune with nothing to prune)
1 Error (running outside a repository, zero candidates, a failing git command, and so on; the message goes to stderr). gz fetch --siblings returns 1 if even one repository fails to fetch (the remaining repositories still run to the end and are then summarised). gz pull likewise returns 1 if even one branch fails to integrate (the remaining branches still run to the end and are summarised; a failed branch is left in its pre-integration state).
2 Invalid command line (including bare gz and bare gz stash, where clap prints the help)
130 The fuzzy finder was aborted (Esc / Ctrl-C), or a confirmation prompt was declined. No git operation is performed

Run outside a git repository, fuzgit prints “Not a git repository. Run this command inside a git repository” and exits non-zero.

コード意味
0 正常終了(gz status の変更ゼロ、gz diff の差分ゼロ、gz pull --rebase の behind ゼロ、gz worktree prune の対象ゼロを含む)
1 エラー(リポジトリ外での実行、候補が 0 件、git コマンドの失敗など。メッセージは標準エラーへ)。 gz fetch --siblings は 1 件でも取得に失敗すると 1 になります (残りのリポジトリは最後まで実行したうえで集計します)。 gz pull も 1 件でも取り込みに失敗すると 1 になります (残りのブランチは最後まで実行したうえで集計します。失敗したブランチは取り込み前の状態のままです)
2 コマンドライン引数が不正(引数なしの gz / gz stash を含む。clap がヘルプを表示)
130 fuzzy finder の中断(Esc / Ctrl-C)、または確認プロンプトでの否認。git 操作は実行されない

git リポジトリ外で実行した場合は「git リポジトリではありません」と表示して非ゼロ終了します。

Out of scope / unsupportedスコープ外・非対応 #

Features deliberately left unimplemented. All of them can be done with plain git.

ItemReason
Rebasing or merging a branch that is not checked out (gz pull in bulk) gz pull --rebase / --merge do exist, but only for the current branch. Doing it to a branch you have not checked out would mean temporarily switching the working tree or creating a temporary worktree — both of which put work in progress at risk. And if the k-th of N branches conflicted, truly returning to the pre-pull state would mean rolling back the k−1 that already succeeded, which needs something as dangerous as git reset --hard. Branches you did not check out therefore stay fast-forward only
--siblings / --prune / a positional branch name for gz pull --siblings would explode the number of selection combinations, and the “pick a branch per repository” model does not hold (pull is a write operation that advances local refs in somebody else’s repository, unlike gz fetch --siblings). --prune follows the policy that deleting tracking refs is stated explicitly via gz fetch --prune . A positional argument is not provided because if you can type the name, plain git already suffices (the existing rule of skipping the finder when there is a single candidate already gives a near-non-interactive experience)
Pushing (gz push) There is nothing worth picking. The branch is fixed to the current one, and a repository almost always has a single remote, so the finder would open with one candidate every time. Plain git push already does the job. Force push is out of scope on top of that: destroying remote history cannot be guaranteed safe by a confirmation prompt alone
Reverting a merge commit (git revert -m) Picking the mainline requires understanding the history structure, and a fuzzy finder adds no value to that choice. When one is selected, fuzgit stops before running and points at plain git revert -m 1 <hash>
Running --dry-run inside the gz fetch preview A preview runs anew for every highlighted entry, so a network round trip would block the display. Updated refs are printed by git fetch itself when it runs
Recursive scanning, or a depth option (--depth etc.), for gz fetch --siblings One level below the parent directory already covers the use case; recursion would increase both the scan cost and the blast radius of network traffic. Repositories in deeper levels are not targeted
An option to specify the scan origin of gz fetch --siblings as a path Accepting an arbitrary path would break the assumption shared by every command that the current repository is the origin, and would let traffic be aimed at unintended directories. The origin is fixed to the parent of the worktree root
Per-repository remote selection in gz fetch --siblings It would mean repeating the finder once per repository, which breaks the operation model. Guessing a single remote implicitly is also avoided, in favour of the deterministic rule --all (every registered remote). Excluding a specific remote can be done on the git side with remote.<name>.skipFetchAll
Fetching the branches of gz pull in parallel Its per-remote fetch is effectively a single call (one upstream remote in most repositories), and integrating branches writes to one repository, where the index, ref and packed-refs locks would collide. gz fetch --siblings is parallel, because the targets are separate repositories (see Network operations)
Including bare repositories in the --siblings candidates There is no working tree for the fetched refs to land in. A bare repository such as foo.git falls outside the scan condition (the existence of <directory>/.git) and is never a candidate; a repository that has <directory>/.git but is configured bare is excluded and counted in the exclusion count shown in the header. This is a feature for the repositories you work in day to day
Following symlinked sibling directories The scan range is limited to real entries directly under the parent directory, preventing the set of contacted hosts from expanding unexpectedly
A [y/N] confirmation before gz fetch --siblings runs Making a multi-selection and pressing Enter is already an explicit decision, and a second confirmation is redundant. A fetch also does not damage local history (the scope of --prune is shown in the finder header)
Bulk operations other than fetch across sibling repositories (bulk status, bulk pull, etc.) fetch is the one operation suited to bulk execution: read-direction, non-destructive and safe to re-run. Extending this to other operations would need its own requirements
Custom signal handling during --siblings (e.g. Ctrl-C to skip the rest) With inherited stdio, SIGINT reaching the process group and terminating fuzgit with it is the expected behaviour. Custom control would be excessive
Automatic retry of failed repositories The existing policy of adding no timeouts and no retries continues. The names of failed repositories appear in the summary line, so the user can decide about re-running
tag vs tag in gz diff Rarely needed. Tags are still offered as the base of gz branch create, which is where they earn their place
gz worktree move / lock / unlock / repair Rarely needed day to day, and each is a single plain git command
gz worktree remove --force --force (forcing a locked worktree) A lock is an explicit “do not delete” mark. One --force (discarding modified and untracked files) is offered; nothing goes past the lock
A text input UI for branch names and worktree paths skim has no text input UI. Names and paths are positional arguments (gz branch create <name> / gz worktree add <name>) and the fuzzy selection is limited to the starting point / the branch. gz worktree add also never suggests a directory name automatically
Hunk-level partial commit / stage File-level selection covers the use case. Hunk selection does not fit skim’s candidate model (one line = one item)
Running the autosquash rebase automatically after gz fixup History rewriting is never run automatically; the steps are printed to stderr and nothing more
A recovery menu for cherry-pick / revert / fetch The recovery menu is limited to merge / rebase. Conflicts in cherry-pick / revert are left to git’s standard message and its --continue / --abort guidance
“Open the conflicted file in an editor” That would launch $EDITOR directly without going through git, which needs a separate design decision to stay consistent with the no-shell policy

意図的に実装していない機能です。いずれも素の git で実行できます。

項目理由
チェックアウトしていないブランチの rebase / merge(gz pull の一括取り込み) gz pull --rebase / --merge は存在しますが、 対象は現在のブランチのみです。チェックアウトしていない ブランチに対して行うには作業ツリーを一時的に切り替えるか一時 worktree を 作るしかなく、いずれも進行中の作業を危険に晒します。また N 本中 k 本目で コンフリクトした場合、真に「pull 前」へ戻すには成功済みの k−1 本も 巻き戻す必要があり、git reset --hard 相当の危険な操作なしには 成立しません。そのため、チェックアウトしていないブランチは fast-forward のみです
gz pull の --siblings / --prune / ブランチ名の位置引数 --siblings は選択の組み合わせが爆発し、リポジトリごとにブランチを選ぶ 操作モデルが成立しない(pull は他人のリポジトリのローカル参照を進める書き込み操作であり、 gz fetch --siblings と性質が異なる)。--prune は追跡参照の削除を gz fetch --prune として明示指定する方針。 位置引数は、名前を打てるなら素の git で足りるため設けない(候補が 1 件なら finder を 省略する既存原則で非対話に近い使い勝手は確保される)
push(gz push) 選ぶ価値のある軸が無い。対象ブランチは現在のブランチに固定され、リモートは 1 つだけのリポジトリがほとんどであるため、候補 1 件の finder が毎回開くだけになる。素の git push で足りる。加えて force push は、リモート履歴の破壊を確認プロンプトだけでは担保できないためスコープ外
マージコミットの revert(git revert -m) mainline の選択は履歴構造の理解を要し、fuzzy finder で「選ぶ」価値が乗らない。 選択された場合は実行前に停止し、素の git revert -m 1 <hash> を案内する
gz fetch のプレビューでの --dry-run 実行 プレビューは選択項目ごとに都度実行されるため、ネットワーク往復が描画をブロックする。更新された参照は git fetch 自身が実行時に表示する
gz fetch --siblings の再帰探索・探索深度の指定(--depth 等) 親ディレクトリ直下の 1 階層で用途を満たせており、再帰は走査コストとネットワークの影響範囲を同時に増やす。深い階層のリポジトリは対象にしない
gz fetch --siblings の探索起点をパスで指定するオプション 任意パスを受け取ると「現在のリポジトリを起点にする」という全コマンド共通の前提が崩れ、意図しないディレクトリへ通信を飛ばせてしまう。起点はワークツリー root の親に固定する
gz fetch --siblings でのリポジトリごとのリモート選択 リポジトリ数だけ finder を繰り返すことになり操作モデルが成立しない。暗黙に 1 つのリモートを 推測するのも避け、--all(登録済みの全リモート)という決定的な規則を採る。 特定リモートの除外は git 側の remote.<name>.skipFetchAll で行える
gz pull のブランチ取り込みの並列化 リモートごとの fetch は対象数が実質 1 件(大半のリポジトリで upstream のリモートは 1 つ)であり、ブランチの取り込みは同一リポジトリへの書き込みで index・参照・packed-refs の lock が競合する。gz fetch --siblings は対象が別リポジトリであるため並列に実行する(ネットワーク操作について参照)
bare リポジトリを --siblings の候補に含めること fetch した参照の反映先となる作業ツリーが無い。foo.git のような bare は 走査条件(<ディレクトリ>/.git の存在)から外れて候補にならず、 <ディレクトリ>/.git を持つが bare 設定のリポジトリは除外件数に数えて ヘッダーへ示す。日常的に作業するリポジトリを対象とする機能である
シンボリックリンクの兄弟ディレクトリを辿ること 走査範囲を「親ディレクトリ直下の実体」に限定し、通信先が予期せず広がることを防ぐ
gz fetch --siblings 実行前の [y/N] 確認プロンプト 複数選択して Enter を押すこと自体が明示的な決定であり、二重確認は冗長。fetch はローカル履歴を壊さない操作でもある(--prune の適用範囲は finder のヘッダーで示す)
兄弟リポジトリへの fetch 以外の一括操作(一括 status / 一括 pull 等) fetch は「読み取り方向・非破壊・失敗しても再実行できる」唯一の一括実行に適した操作である。他の操作へ広げるのは別途要件から起こす
--siblings 実行中の独自シグナルハンドリング(Ctrl-C で残りをスキップ等) 継承 stdio のままプロセスグループへ SIGINT が届き fuzgit ごと終了するのが期待挙動。独自制御は過剰
失敗したリポジトリの自動リトライ fuzgit 側でタイムアウト・リトライを行わない既存方針を継続する。失敗したリポジトリ名は集計行に出るため、再実行の判断はユーザーが行える
gz diff の tag vs tag 使用頻度が低い。タグは gz branch create のベース候補として引き続き選べる
gz worktree の move / lock / unlock / repair 日常頻度が低く、いずれも素の git で 1 コマンドで済む
gz worktree remove --force --force(locked な worktree の強制削除) lock は「消すな」という明示の印。--force 1 回(変更済み・未追跡ファイルの破棄)までを提供し、それを越える手段は用意しない
ブランチ名・worktree パスのテキスト入力 UI skim にテキスト入力 UI は無い。名前・パスは位置引数 (gz branch create <name> / gz worktree add <name>)とし、 fuzzy 選択は作成元・ブランチに限定する。gz worktree add のディレクトリ名の 自動提案も行わない
hunk 単位の部分 commit / stage ファイル単位の選択で用途を満たす。hunk 選択は skim の候補モデル(行=項目)に乗らない
gz fixup 後の autosquash rebase の自動実行 履歴改変の自動実行は行わない。手順を標準エラーへ表示するに留める
cherry-pick / revert / fetch の復帰メニュー 復帰メニューは merge / rebase に限定する。cherry-pick / revert のコンフリクトは git の標準メッセージと --continue / --abort の案内に委ねる
コンフリクトファイルを「エディタで開く」 git を介さず $EDITOR を直接起動する経路になり、シェル非経由方針との整合に別途設計判断が必要

Debuggingデバッグ #

$ FUZGIT_DEBUG=1 gz branch
[fuzgit] (B) git log --color=always --oneline --decorate -n 50 feature -- [LANGUAGE=en]
[fuzgit] (B) git switch feature [LANGUAGE=en]

$ FUZGIT_DEBUG=1 gz status
[fuzgit] (A) (cwd: .) git status --porcelain -z --untracked-files=all [LC_MESSAGES=C]

Setting the environment variable FUZGIT_DEBUG=1 prints every git command fuzgit runs to stderr.

  • It is enabled only when the value is exactly 1. Unset, empty, 0, true and anything else are all disabled.
  • The destination is always stderr (stdout is kept free for piping hashes, tag names and paths).
  • The log lists the argument array as-is. Because it is printed on every preview generation, it can visibly mix with the drawing while the fuzzy finder is on screen.
  • Commands that target another directory are printed as [fuzgit] (A|B) (cwd: <directory>) git ....
  • (A) marks a call whose output fuzgit parses: it always runs with LC_MESSAGES=C so parsing cannot depend on the environment. (B) marks a call whose output you read (inherited stdio, previews, reports passed through): the resolved display language is propagated as LANGUAGE=<ja|en>. The variables that were set or removed are listed in brackets at the end of the line, so the log answers “which language did this git speak?” directly. See Language.

環境変数 FUZGIT_DEBUG=1 を指定すると、fuzgit が実行した git コマンドを 標準エラーへ出力します。

  • 有効になるのは値が厳密に 1 の場合だけです。未設定・空文字・0・true などはすべて無効です。
  • 出力先は必ず標準エラーです(標準出力はハッシュ・タグ名・パスのパイプ用途のために空けてあります)。
  • ログにはコマンドの引数配列がそのまま並びます。プレビュー生成のたびに出力されるため、fuzzy finder の表示中は画面の描画と混ざって見えることがあります。
  • 別ディレクトリを対象に実行するコマンドは [fuzgit] (A|B) (cwd: <ディレクトリ>) git ... の形式で出力します。
  • (A) はfuzgit が出力をパースする呼び出しで、実行環境に よってパースが壊れないよう常に LC_MESSAGES=C で実行します。 (B) はユーザーが出力を読む呼び出し(継承 stdio・プレビュー・ そのまま提示する報告)で、解決された表示言語を LANGUAGE=<ja|en> として伝播します。設定・削除した環境変数は 行末の角括弧に併記されるため、「この git は何語で話したのか」がログから直接 分かります。表示言語も参照してください。

Development開発 #

Quality gate品質ゲート

Confirm that all of these succeed, in this order, on every change.

以下の順にすべて成功することを、変更のたびに確認してください。

cargo build
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo test

Testing policyテスト方針

  • Interactive paths that take over the terminal (the TUI) are outside the scope of automated tests and are verified manually.
  • Automated tests cover pure logic (formatting candidates, assembling git arguments, parsing git status / git stash list / git worktree list) as unit tests, and non-interactive paths (--help, running outside a repository, zero candidates) as integration tests with assert_cmd.
  • Paths involving the network (gz fetch / gz pull) are not automated; only their non-interactive paths (no remotes, no upstream, zero candidates and similar errors) are verified. Real-world checks use a local bare repository as the remote and never connect to an external network.
  • 端末(TUI)を占有する対話パスは自動テストの対象外とし、手動確認とします。
  • 自動テストの対象は、候補の整形・git 引数の組み立て・git status / git stash list / git worktree list のパースなどの純ロジック (単体テスト)と、--help・リポジトリ外実行・候補 0 件などの非対話パス (assert_cmd による統合テスト)です。
  • ネットワークを伴う経路(gz fetch / gz pull)は 自動テスト化せず、非対話パス(リモートゼロ・upstream 未設定・ 候補ゼロなどのエラー)のみを検証します。実機確認はローカルの bare リポジトリを remote に設定して行い、外部ネットワークへは接続しません。

Design principles設計方針

  • Read with gix, write by shelling out to git. gix does not provide the write operations for switch / cherry-pick / restore / add / stash, so they are delegated to the system git in order to keep behavioural compatibility. The worktree listing is read with git worktree list --porcelain -z rather than gix (gix cannot handle the main worktree or the checked-out branch).
  • External commands always run as Command::new("git").args([...]), i.e. with an argument array, never through a shell. Paths that come from the user are passed after -- as the pathspec :(top,literal)<path>, removing any room for them to be interpreted as options or wildcards.
  • Previews do not hand skim a shell command string; git is executed from Rust and the result is displayed, which structurally prevents injection through candidate strings.
  • Positional arguments that cannot be protected with --, such as branch names and commit hashes (switch / merge / rebase / fixup / revert / diff), are passed to git only after checking that the selection is present in the candidate list. The only values that get through originate from real refs enumerated by gix. gz pull verifies that branch.<name>.remote is a registered remote name before running git fetch (the configuration can hold a URL directly, so without the check it could connect to something that was never enumerated). The refspec gz pull uses for branches that are not checked out carries no forcing +, so git rejects non-fast-forward updates and local commits cannot be lost.
  • No network access during candidate generation or previews.
  • The only command that touches anything outside the current repository is gz fetch --siblings. The scan origin is always derived from the current repository’s worktree root (no path argument is accepted) and is limited to one level under the parent directory. Sibling repositories are opened with gix::open, not gix::discover (discover walks upwards, so a directory without a .git would grab a repository above the scan range). The selection is checked for an exact match against the candidate list, using normalised absolute paths, before it becomes a target.
  • Destructive operations (discarding changes with restore, stash drop, branch delete / cleanup, history rewriting with rebase, the integration performed by pull --rebase / --merge, worktree remove / prune, and aborting a merge / rebase) are confirmed first. merge is confirmed too, showing how many commits will come in and the conflict prediction.
  • History rewriting is never run automatically (gz fixup only prints the autosquash steps; it does not rebase).
  • Pushing is not provided at all, so neither is force push. Use plain git push.
  • 読み取りは gix、書き込みは git へのシェルアウト。 gix は switch / cherry-pick / restore / add / stash の書き込み操作を提供していないため、 挙動互換性を優先してシステムの git に委譲しています。worktree の一覧だけは gix ではなく git worktree list --porcelain -z を読みます(gix は main worktree と チェックアウト中のブランチを扱えないため)。
  • 外部コマンドは常に Command::new("git").args([...]) の引数配列渡しで 実行し、シェルを一切経由しません。ユーザー由来のパスは -- の後ろに :(top,literal)<path> のパススペックとして渡し、オプションや ワイルドカードとして解釈される余地を排除しています。
  • プレビューは skim にシェルコマンド文字列を渡す方式ではなく、Rust 側で git を実行した 結果を表示する方式を採り、候補文字列由来のインジェクションを構造的に防いでいます。
  • ブランチ名・コミットハッシュのように -- で保護できない位置引数 (switch / merge / rebase / fixup / revert / diff)は、 選択結果が候補一覧に含まれることを確かめてから git へ渡します。 渡る値は gix が列挙した実在の参照に由来するものだけです。gz pull は branch.<name>.remote が登録済みのリモート名であることを検証して から git fetch します(設定には URL を直接書けるため、検証しないと 列挙していない対象へ接続することになります)。gz pull が チェックアウトしていないブランチを更新する refspec には、強制更新の + を 付けません(非 fast-forward の更新を git に拒否させ、ローカルのコミットを 失わせないためです)。
  • 候補生成・プレビューでネットワークアクセスを行いません。
  • 現在のリポジトリ以外に触れるのは gz fetch --siblings だけです。 走査の起点は常に現在のリポジトリのワークツリー root から導出し(パス引数を受け取りません)、 親ディレクトリ直下の 1 階層に限定します。兄弟のリポジトリを開くのは gix::open であり gix::discover ではありません (discover は上位へ遡るため、.git を持たないディレクトリから 走査範囲より上のリポジトリを掴んでしまいます)。選択結果は正規化済みの絶対パスで 候補一覧と完全一致することを確かめてから実行対象にします。
  • 破壊的操作(restore による変更破棄、stash drop、 branch delete / cleanup、rebase による履歴改変、 pull --rebase / --merge の取り込み、 worktree remove / prune、 merge / rebase の abort)は実行前に確認を挟みます。merge にも 確認を設け、取り込まれるコミット数とコンフリクト予測を提示します。
  • 履歴改変の自動実行は行いません(gz fixup は autosquash の手順を表示するだけで rebase しません)。
  • push 自体を提供しないため、force push も提供しません。素の git push を使ってください。

Module layoutモジュール構成

src/
├── main.rs          # entry point (parse the CLI, dispatch, exit code)
├── lib.rs
├── cli.rs           # command definitions via clap derive
├── error.rs         # domain errors via thiserror
├── finder.rs        # skim wrapper (candidates, preview, abort detection)
├── git/
│   ├── repo.rs      # opening the repository with gix
│   ├── read.rs      # reading candidate data via gix / git
│   ├── siblings.rs  # scanning repositories under the parent directory (gz fetch --siblings only)
│   └── exec.rs      # running git (run_git / capture_git) and the debug log
└── commands/        # the use case implementation of each subcommand
src/
├── main.rs          # エントリポイント(CLI パース → dispatch → 終了コード)
├── lib.rs
├── cli.rs           # clap derive によるコマンド定義
├── error.rs         # thiserror によるドメインエラー
├── finder.rs        # skim ラッパー(候補・プレビュー・中断判定)
├── git/
│   ├── repo.rs      # gix によるリポジトリのオープン
│   ├── read.rs      # gix / git による候補データの読み取り
│   ├── siblings.rs  # 親ディレクトリ直下のリポジトリ探索(gz fetch --siblings 専用)
│   └── exec.rs      # git の実行(run_git / capture_git)とデバッグログ
└── commands/        # 各サブコマンドのユースケース実装

Licenseライセンス #

MIT License. The full text is in LICENSE.

MIT License. 全文は LICENSE を参照してください。