💤

lazygitのカスタムキーマップにシェルスクリプトで複雑な処理を仕込む

に公開

はじめに

lazygitは少ないキー操作で様々なGit操作を行えるターミナルUIツールです。
本稿ではlazygit自体の解説は割愛します。
mozumasuさんの記事などを参照してください。

lazygitにはgit addgit commitgit pushのような基本的なコマンドのキーマップは当然デフォルトで用意されていますが、カスタムキーマップを自分で割り当てることができます。
lazygitでは「カスタムコマンド」と呼ばれていますので、以降は「カスタムコマンド」と呼ぶことにします[1]

カスタムコマンドの設定

カスタムコマンドは以下のようにconfig.yml中のcustomCommandsキー下に設定を記載します。
config.ymlを置くべきパスはOSごとに異なるので公式ドキュメントで確認してください。

config.yml
customCommands:
- key: "Y"
  context: "commits"
  command: "printf {{.SelectedCommit.Hash}} | head -c7 | pbcopy"
  description: "ショートコミットハッシュをクリップボードにコピー"

こう設定すると、コミットのリストが表示されているタブでYキーを押すと、選択中のコミットのショートハッシュをクリップボードにコピーすることができます[2]

設定した各キーについて簡単に説明します。
必須キーはcontextcommandです。
なぜか必須ではないですが、keyでコマンドを実行するキーマップを指定します。

commandの部分に実行するコマンドを記載します。

contextはどのタブで実行するかを指定します。
タブは、ステージングを行うファイルタブ、ブランチを管理するブランチタブ、コミットを確認するコミットタブなどがあります。
contextはそれぞれfileslocalBranchescommitsが対応しています。
上記のカスタムコマンドはコミットを指定して実行しますが、これが差分ファイル一覧が表示されているファイルタブ上で実行しようとすると意味不明になりますので、コミット一覧が表示されているコミットタブで実行されるようにcontext: "commits"と指定しています。
これにより同じキーでもタブごとに異なるコマンドを割り当てることができます。
またcontext: "global"と指定すると、どのタブでも実行可能なコマンドになります。
したがって新たにキーマップを定義する際には、指定するcontextglobalで定義済みのキーと衝突しないように注意する必要があります。
プリセットのキーマップは公式ドキュメントで確認できます。

この例は単純ですが、より複雑な処理を行いたい場合、例えばifで分岐したいとか、ghコマンドで取得した値を使用したいということをやり始めると、難読ワンライナーになっていきます。
YAML記法で改行して書く方法は考えられますが、いずれにせよ可読性が低くなりがちです。

本記事ではこの問題を解決する方法について解説します。
アイデアは単純でcommand./copy-short-hash.shのように処理内容を書いたシェルスクリプトを指定して実行するだけです。
こうすることで複雑な処理もシェルスクリプトに切り出して書くことができます。

アイデアはこれだけですが、上記の例でも{{...}}で囲まれた独自の書き方が見えているように、lazygit特有のお作法がいくつかありますのでそれを説明するというのが本記事の内容となります。

参考資料

カスタムコマンドに関わる機能を網羅的に解説を行うわけではありませんので、カスタムコマンドの書き方に関する詳細は公式ドキュメントを参照してください。

また、lazygitのwikiにはCustom Commands Compendiumというページがあり、カスタムコマンドの例がいくつか紹介されています。

カスタムコマンドをどう設定すれば分からない場合はdeepwikiなどに相談するのも良いでしょう。

シェルスクリプトを利用したカスタムコマンドの設定方法

以下では、実際に私が使用している、upstreamのプルリクエストをIDで指定してworktreeとしてチェックアウトするカスタムコマンドを例として用います。

コマンドの設定

コマンドの設定は

config.yml
- key: "V"
  prompts:
    - type: "input"
      title: "PR id (V):"
  command: "$HOME/dotfiles/lazygit/pr-checkout-as-worktree.sh {{index .PromptResponses 0}}"
  context: "global"
  loadingText: "checking out PR as worktree"
  description: "PR No.を指定してチェックアウト (worktree)"
  output: log

です。customCommandsキーの下に追加することを注意してください。

ポイントはやはりcommandの部分です。
まず実行するシェルスクリプトは絶対パスで指定します
相対パスはlazygitの実行ディレクトリ、すなわちプロジェクトパスに依存するため、シェルスクリプトのパスを相対パスで指定できません。

基本的に実行するシェルスクリプトはlazygitの設定ファイルと同じディレクトリに置くと良いでしょう。
もちろん別のディレクトリでも問題ありません。
私は設定ファイルをシンボリックリンクを用いたdotfiles方式で管理しているので少々ややこしいことになっています。
リンク先のdotfilesディレクトリにはconfig.yml とシェルスクリプトの両方が存在しますが、リンクを貼っているのがconfig.yml のみのため、config.ymlを置くべきリンク元ディレクトリにシェルスクリプトはありません。

次に、{{index .PromptResponses 0}}という部分ですが、これはVキーを押した際、コマンド実行前にユーザーが入力した値をコマンドで使用するためのものです。
このようにlazygitが提供するテンプレートを引数としてシェルスクリプトに渡すことが第2のポイントです。
具体的にどのようなテンプレートが使用できるかは公式ドキュメントを参照してください。

続いて、outputlogにするかterminalにするかどうかも考慮する点だと思います。
logでは、コマンドを実行するとlazygitを開いたまま画面の右下にあるコマンドログに出力が表示されます。
terminalにすると、コマンド実行前にlazygitの画面が一旦閉じて、シェルスクリプトの実行結果がターミナルに表示されます。
これは大雑把に言えば非同期処理か同期処理かの違いになります。
一旦lazygitを止めて処理すべき内容であればterminalを選ぶと良いでしょう。
なお、outputには他にも設定値がありますので、他の設定値が知りたい方は公式ドキュメントを参照してください。

シェルスクリプトの内容

実行しているpr-checkout-as-worktree.shは以下のような内容です。
具体的な中身については特に説明しませんが、ghコマンドなどを使用してチェックアウトするブランチや作業中のレポジトリ名を取得したいためコマンドが長くなったという例です。

#!/bin/bash
set -eu
pr_id="$1"
branch=$(gh pr view "$pr_id" --json headRefName --jq .headRefName)
repo_name=$(basename "$(git rev-parse --show-toplevel)")
dir_name="${repo_name}-${branch//\//_}"
git fetch upstream "pull/${pr_id}/head:${branch}"
git worktree add "../${dir_name}" "$branch"

最初の行にあるシェバンの#!/bin/bashは必要です。
そして以下のように実行権限を付与する必要があります。

chmod +x "$HOME/dotfiles/lazygit/pr-checkout-as-worktree.sh"

これが面倒な場合はcommandの部分を

command: "bash $HOME/dotfiles/lazygit/pr-checkout-as-worktree.sh {{index .PromptResponses 0}}"

のように書き換えることもできます。

最後になりますが、シェルは指定することをおすすめします。
lazygitは$SHELL環境変数を参照してコマンドを実行するシェルを決定するため、想定外のシェルで評価され予期せぬ動作となる可能性があるからです。

終わりに

正直特に難しいことはないのですが、lazygitのカスタムコマンドの雰囲気を伝えたかったのもあり、記事にしました。
カスタムコマンドの紹介記事としては説明をかなり省略しましたが、公式ドキュメントがしっかり書かれているので、そちらを参照してください。

脚注
  1. 厳密に言うとcustom command keybindingと呼ばれていますが、長いので省略します。 ↩︎

  2. ちなみにフルハッシュはプリセットのyキーでコピーできます。 ↩︎

GitHubで編集を提案

Discussion