Go製Git操作ツール『ggc』の紹介
ggcとは
ggcはGo製のGitワークフローツールだ。日常的なGitサブコマンドを一貫したインターフェースで提供し、コマンド名を暗記せずとも操作できるインタラクティブFuzzy検索TUIを搭載する。Workflow Mode、プレースホルダー対応のカスタムエイリアス、階層型キーバインドプロファイルシステムなど多くの機能を持つ。本記事ではその全てを解説する。Awesome Goにも掲載されているツールだ。
デモ
| ブランチ管理 | CLIワークフロー | インタラクティブ概要 |
|---|---|---|
![]() |
![]() |
![]() |
ggcを使うメリット
コマンドを覚えなくていい
Gitには200以上のサブコマンドがある。日常的に使うもの以外は「stash applyのIDの書き方がわからない」「amend --no-editのフラグ名を思い出せない」と手が止まる。ggcのインタラクティブモードなら、bdと打つだけでbranch deleteが絞り込まれ、caでcommit amendも見つかる。探索コストがゼロになる。
代表的なユースケース
毎日のadd → commit → push: Workflow Modeで3コマンドをキューに積んでおけば、次回以降はxを押すだけで完結する。コミットメッセージはプレースホルダーとして実行時に入力できるため、毎回ワークフローを組み直す必要はない。
ブランチ管理: branch checkout remoteでリモートから追跡ブランチを作成、branch delete mergedでマージ済みブランチを一括削除など、複合操作をわかりやすい名前のコマンドとして提供している。gitのフラグを調べる必要がない。
Fixupワークフロー: commit fixup <commit> → rebase autosquashのシーケンスをエイリアスに登録しておけば、fixupコミットの適用をコマンド1つで実行できる。
hookの管理: hook list / hook enable / hook disableでプロジェクトのGit hookをGUIなしで手軽に管理できる。
他のGitツールとの比較
| ツール | 方式 | 強み | ggcとの違い |
|---|---|---|---|
git(素) |
CLI | 完全な機能・フラグで細かく制御 | コマンド名・フラグの記憶が必要 |
lazygit |
TUI専用 | 豊富なパネルUI | TUIのみ、スクリプトへの組み込みが難しい |
tig |
TUI(読み取り) | ログ・差分の視覚化 | 操作機能なし |
gh |
CLI | GitHub連携 | ローカルGit操作は対象外 |
| gitエイリアス | CLI | 完全にカスタム可能 | 発見性なし・全部自分で書く必要あり |
ggcの強みはCLI/TUIを自由に使い分けられることだ。スクリプトからはggc push forceをそのまま呼び出せる。インタラクティブに使いたければggcと打つだけでfuzzy検索が起動する。どちらのパスも同じRoute()で処理するため挙動が一致する。
CLI/インタラクティブモードのアーキテクチャ
ggcには2つの実行パスがある。直接コマンドを実行するCLIパスと、フルスクリーンTUIを起動するインタラクティブパスだ。cmd/execute.goのExecute()メソッドがどちらのパスを連るかを決定する唯一のエントリーポイントだ。
func (c *Cmd) Execute(args []string) error {
if len(args) == 0 {
c.Interactive()
return nil
}
cmdName, cmdArgs := args[0], args[1:]
// エイリアスかチェック
if c.configManager != nil && c.configManager.GetConfig().IsAlias(cmdName) {
return c.executeAlias(cmdName, cmdArgs)
}
// 通常コマンド
return c.Route(args)
}
引数がゼロの場合はInteractive()でTUIを起動する。エイリアス名を検出した場合はexecuteAlias()で展開し実行する。それ以外はRoute()に渡す。このRoute()関数はCLIパス、エイリアス展開、Workflow実行エンジンの3つすべてが共有する実装だ。
インタラクティブモードの設計
インタラクティブTUIは1つのターミナル画面を共有する2つのサブモードに分かれている。Ctrl+tでトグルする。
Search Mode — Fuzzy Search
Search Modeがデフォルトだ。全コマンドの一覧を表示し、入力に応じてリアルタイムにフィルタリングする。スコアリングはinternal/interactive/fuzzy.goのmatchPattern()関数が担当する。
func matchPattern(textRunes, patternRunes []rune) (bool, matchMetadata) {
meta := matchMetadata{firstIndex: -1, lastIndex: -1}
textIdx := 0
patternIdx := 0
for textIdx < len(textRunes) && patternIdx < len(patternRunes) {
if textRunes[textIdx] == patternRunes[patternIdx] {
if meta.firstIndex == -1 {
meta.firstIndex = textIdx
}
if meta.lastIndex != -1 {
meta.gapScore += textIdx - meta.lastIndex - 1
}
meta.lastIndex = textIdx
patternIdx++
}
textIdx++
}
if patternIdx != len(patternRunes) {
return false, meta
}
return true, meta
}
アルゴリズムは古典的な部分列マッチングだ。パターンの全文字がテキスト内に順序通り現れればマッチと判定する(連続不要)。matchMetadata構造体は3つのシグナル—firstIndex(マッチ開始位置)、gapScore(文字間の距離合計)、lastIndex—を蓄積し、matchScoreとしてソートに利用する。致密で早い位置のマッチが、散在した遅いマッチより高くランク付けされる。
Workflow Mode
Ctrl+tを押すとinternal/interactive/ui_mode.goのToggleWorkflowView()が呼び出される。
func (ui *UI) ToggleWorkflowView() {
if ui.state.IsWorkflowMode() {
ui.enterSearchMode()
return
}
ui.enterWorkflowMode()
}
Workflow Modeでは、検索リストの任意アイテムでTabを押すことでggcコマンドのシーケンス(例:add → commit → push)を組み立てられる。xを押すとWorkflowExecutor.Execute()が順次実行する。
func (we *WorkflowExecutor) Execute(workflow *Workflow) error {
steps := workflow.GetSteps()
if len(steps) == 0 {
return fmt.Errorf("workflow is empty")
}
for i, step := range steps {
resolvedArgs, canceled := resolveStepPlaceholders(we.ui, step)
if canceled {
return ErrWorkflowCanceled
}
parts := append([]string{step.Command}, resolvedArgs...)
if err := we.router.Route(parts); err != nil {
return fmt.Errorf("step %d/%d failed: %w", i+1, len(steps), err)
}
}
return nil
}
各ステップの前にresolveStepPlaceholders()が引数内の<name>トークンをスキャンし、存在する場合はインタラクティブに入力を求める。事前にコミットメッセージをハードコードする必要はなく、実行時にプロンプトで單やかに入力できる。
利用可能なコマンド一覧
ggcが提供する全コマンドのリファレンスだ。CLIから直接呼び出せるほか、インタラクティブモードのFuzzy検索で同じ名前を入力して検索できる。
| コマンド | 説明 |
|---|---|
add . |
すべての変更をインデックスに追加 |
add <file> |
指定ファイルをインデックスに追加 |
add interactive |
インタラクティブに変更を追加 |
add patch |
インタラクティブに変更を追加(パッチモード) |
help |
メインヘルプメッセージを表示 |
help <command> |
指定コマンドのヘルプを表示 |
reset |
origin/<ブランチ>へハードリセットし作業ディレクトリをクリーン |
reset hard <commit> |
指定コミットへハードリセット |
reset soft <commit> |
ソフトリセット:HEADを移動し変更はステージに保持 |
branch checkout |
既存ブランチへ切り替え |
branch checkout remote |
リモートからローカル追跡ブランチを作成してチェックアウト |
branch contains <commit> |
指定コミットを含むブランチを表示 |
branch create |
新しいブランチを作成してチェックアウト |
branch current |
現在のブランチ名を表示 |
branch delete |
ローカルブランチを削除 |
branch delete merged |
マージ済みローカルブランチを削除 |
branch info <branch> |
ブランチの詳細情報を表示 |
branch list local |
ローカルブランチを一覧表示 |
branch list remote |
リモートブランチを一覧表示 |
branch list verbose |
ブランチの詳細一覧を表示 |
branch move <branch> <commit> |
ブランチを指定コミットへ移動 |
branch rename <old> <new> |
ブランチをリネーム |
branch set upstream <branch> <upstream> |
ブランチのアップストリームを設定 |
branch sort [date|name] |
日付またはアルファベット順にブランチを一覧表示 |
commit <message> |
メッセージ付きでコミット作成 |
commit allow empty |
空コミットを作成 |
commit amend |
前のコミットを修正(エディタ起動) |
commit amend no-edit |
コミットメッセージを編集せずに修正 |
commit fixup <commit> |
指定コミットを対象にfixupコミットを作成 |
log graph |
グラフ付きでログを表示 |
log simple |
シンプルな履歴ログを表示 |
fetch |
リモートからフェッチ |
fetch prune |
フェッチして古い参照を削除 |
pull current |
現在のブランチをリモートからプル |
pull rebase |
プルしてリベース |
push current |
現在のブランチをリモートへプッシュ |
push force |
現在のブランチを強制プッシュ |
remote add <name> <url> |
リモートリポジトリを追加 |
remote list |
すべてのリモートリポジトリを一覧表示 |
remote remove <name> |
リモートリポジトリを削除 |
remote set-url <name> <url> |
リモートURLを変更 |
status |
作業ツリーの状態を表示 |
status short |
簡潔なステータスを表示(porcelain形式) |
clean dirs |
追跡されていないディレクトリを削除 |
clean files |
追跡されていないファイルを削除 |
clean interactive |
インタラクティブにファイルを削除 |
restore . |
作業ディレクトリのすべてのファイルをインデックスから復元 |
restore <commit> <file> |
指定コミットからファイルを復元 |
restore <file> |
作業ディレクトリのファイルをインデックスから復元 |
restore staged . |
すべてのファイルをアンステージ |
restore staged <file> |
ファイルをアンステージ(HEADからインデックスへ復元) |
diff |
変更を表示(git diff HEAD) |
diff head |
HEADとの差分を表示 |
diff staged |
ステージ済みの変更を表示 |
diff unstaged |
ステージされていない変更を表示 |
tag annotated <tag> <message> |
注釈付きタグを作成 |
tag create <tag> |
タグを作成 |
tag delete <tag> |
タグを削除 |
tag list |
すべてのタグを一覧表示 |
tag push |
タグをリモートへプッシュ |
tag show <tag> |
タグ情報を表示 |
config get <key> |
設定値を取得 |
config list |
すべての設定を一覧表示 |
config set <key> <value> |
設定値をセット |
hook disable <hook> |
フックを無効化 |
hook edit <hook> |
フックの内容を編集 |
hook enable <hook> |
フックを有効化 |
hook install <hook> |
フックをインストール |
hook list |
すべてのフックを一覧表示 |
hook uninstall <hook> |
既存のフックをアンインストール |
rebase <upstream> |
現在のブランチを<upstream>にリベース |
rebase abort |
進行中のリベースを中止 |
rebase autosquash |
--autosquashオプション付きインタラクティブリベース |
rebase continue |
進行中のリベースを継続 |
rebase interactive |
インタラクティブリベース |
rebase skip |
現在のパッチをスキップして継続 |
stash |
現在の変更をスタッシュ |
stash apply |
スタッシュを削除せずに適用 |
stash apply <stash> |
指定スタッシュを削除せずに適用 |
stash branch <branch> |
スタッシュからブランチを作成 |
stash branch <branch> <stash> |
指定スタッシュからブランチを作成 |
stash clear |
すべてのスタッシュを削除 |
stash create |
スタッシュを作成してオブジェクト名を返す |
stash drop |
最新のスタッシュを削除 |
stash drop <stash> |
指定スタッシュを削除 |
stash list |
すべてのスタッシュを一覧表示 |
stash pop |
最新のスタッシュを適用して削除 |
stash pop <stash> |
指定スタッシュを適用して削除 |
stash push |
変更を新しいスタッシュに保存 |
stash push -m <message> |
メッセージ付きで変更を新しいスタッシュに保存 |
stash save <message> |
メッセージ付きで変更を新しいスタッシュに保存 |
stash show |
スタッシュ内の変更を表示 |
stash show <stash> |
指定スタッシュ内の変更を表示 |
stash store <object> |
スタッシュオブジェクトを保存 |
debug-keys |
現在のキーバインドを表示 |
debug-keys raw |
ターミナルのキーシーケンスをインタラクティブにキャプチャ |
debug-keys raw <file> |
キーシーケンスをキャプチャしてファイルに保存 |
quit |
インタラクティブモードを終了 |
version |
ggcの現在バージョンを表示 |
その他の機能
YAMLによるWorkflow事前定義
エイリアスとは別に、~/.ggcconfig.yamlのworkflows:セクションにワークフローを事前登録できる。TUI起動時に自動でWorkflow Modeに読み込まれ、nキーで作成したワークフローと并列して管理できる。
workflows:
daily:
- "add ."
- "commit -m <message>"
- "push current"
deploy:
- "branch checkout <branch>"
- "pull current"
- "push <branch>"
各ステップの<name>トークンは実行時にプロンプトで入力を求める。xで実行するとその場でメッセージやブランチ名を入力できる。エイリアス(CLIから呼び出す短縮形)との違いは、Workflow Modeで複数コマンドを順次定義・再利用する点にある。
コマンドエイリアス
エイリアスは~/.ggcconfig.yamlで定義する。シンプル形式とシーケンス形式の両方をサポートする。
aliases:
br: branch # シンプル — ggc br list
ci: commit
quick: # シーケンス
- status
- add .
- commit
deploy: # プレースホルダー付きシーケンス
- "branch checkout {0}"
- "pull current"
- "push {0}"
キーバインドプロファイル
インタラクティブUIは4つのビルトインキーバインドプロファイル(default、emacs、vi、readline)をサポートする。キーバインドは6層の優先順に解決される—デフォルト → プロファイル → プラットフォーム → ターミナル → ユーザー設定 → 環境変数オーバーライド。
interactive:
profile: emacs
keybindings:
move_up: "ctrl+p"
move_down: "ctrl+n"
toggle_workflow_view: "ctrl+t"
add_to_workflow: "tab"
クロスプラットフォーム対応
GoReleaserを介してLinux、macOS、Windows(amd64 / arm64 / 386)すべてのpresetバイナリを配布している。インタラクティブTUIの全機能が3プラットフォームで動作する。
シェル補完
ggcはBash、Zsh、Fishのシェル補完スクリプトを同梱している。tools/completions/に生成済みスクリプトが収録されており、make completionsでコマンドレジストリから再生成できる。
# Bash(~/.bash_profile または ~/.bashrc に追記)
if [ -f ~/.ggc-completion.bash ]; then
. ~/.ggc-completion.bash
fi
# Zsh(~/.zshrc に追記)
if [ -f ~/.ggc-completion.zsh ]; then
. ~/.ggc-completion.zsh
fi
# Fish(~/.config/fish/config.fish に追記)
if test -f ~/.ggc-completion.fish
source ~/.ggc-completion.fish
end
有効化するとggc b<Tab>でbranchに補完され、ggc branch <Tab>でサブコマンド一覧が展開される。
統一構文と--セパレーター
ggcは-x/--long形式のフラグを持たない統一構文を採用している。全操作はスペース区切りのサブコマンドで表現する(例: ggc fetch prune、ggc commit allow empty)。--以降の引数はコマンドではなくデータとして扱われるため、先頭に-が付く文字列も安全に渡せる。
# 先頭に - がある引数を渡す場合
ggc commit -- "-fix leading dash"
この設計はCLIの挙動を予測可能にし、スクリプトへの埋め込みを容易にする。
Soft cancel
インタラクティブモードを終了せず、現在の操作を中断してSearch Modeに戻るにはCtrl+G(または特殊文字が続かないEsc)を使う。Ctrl+Cがインタラクティブモードごと終了するのに対し、Ctrl+Gはモードを維持したまま検索画面に戻る。
debug-keysコマンド
キーバインドの動作確認やトラブルシューティングに使える組み込みコマンドだ。
# 現在のキーバインド設定を一覧表示
ggc debug-keys
# ターミナルが送出するキーシーケンスをリアルタイムでキャプチャ
ggc debug-keys raw
# キーシーケンスをファイルに保存
ggc debug-keys raw keydump.txt
ggc debug-keys rawを実行してキーを押すと、ターミナルが実際に送信しているバイト列が表示される。キーバインドが期待どおりに動作しない場合の原因特定に役立つ。
tmuxサポート
tmux環境でキー入力が正しく処理されない場合、.tmux.confに以下を追加する。
set -g xterm-keys on
インストール
# Homebrew (macOS/Linux)
brew install ggc
# インストールスクリプト(最もかんたん)
curl -sSL https://raw.githubusercontent.com/bmf-san/ggc/main/install.sh | bash
# Go install
go install github.com/bmf-san/ggc/v8@latest
まとめ
ggcは、明確な設計思想に基づく柔軟なGitワークフローツールだ。CLIパスとインタラクティブパスは同じRoute()層を共有しており、挙動が一貫している。Fuzzyサーチスコアラーは依存性ゼロの純粋関数であり、Workflow実行エンジンはコマンドディスパッチロジックを重複定義せずにRoute()を再利用する。
- GitHub: bmf-san/ggc


