transientを使ってmagitライクなツールを作ってみる

はてなエンジニア - Qiita Advent Calendar 2025 - Qiita の12/23の記事です。


magitEmacsで動くgitクライアントです。 キーを押すとメニューが開き、メニュー内の表示に従ってオプションやサブコマンドを指定することでgitのコマンドを組み立てて実行するのが特徴です。

言葉で説明するのは難しいですが、ブランチ作成→編集→add→コミット→プッシュはこんな感じで行えます:

例えば「ブランチを作成してチェックアウト」は、

  1. bを押してブランチ関連のメニューを開く
  2. cを押してCheckoutグループのnew branchを選択
  3. ベースブランチの名前を入力(このときカーソル上のブランチがデフォルトで使用される)
  4. 作成するブランチ名を入力

という手順で行っています。

このようなキー操作を抽象化したのが magit/transientです。今回はこのtransientを使ってGitHub CLIの小さいラッパーを作ってみます。プルリクエストを一覧してマージできるまでを目標とします。

まずはステータスバッファを作成してプルリクエスト一覧を表示します。 ここは普通にキーマップとメジャーモードを作り、gh pr list --jsonをパースします。

次に作成したメジャーモードのキーマップでtransientを発動させます。

(transient-define-prefix magh-pr-transient ()
  ["Pull Request"
    ("o" "Open in browser" magh-pr-view)
    ("m" "Merge" magh-pr-merge-transient)])

;;; メジャーモードのキーマップの中で
(define-key map "P" 'magh-pr-transient)

これで、Pキーを押すとプルリクエストのメニューが出てくるようになります。 magh-pr-viewはコマンドで、PR番号を指定してブラウザで開きます。

(defun magh-pr-view ()
  (interactive)
  (let ((pr-number (read-number "PR number: " (magh-pr-number-at-point))))
      (magh-git-call "pr" "view" "--web" (number-to-string pr-number))))

magh-pr-number-at-pointはとりあえずre-search-forwardを使う感じで、 magh-git-callはだいたい(apply #'process-file (find-executable "gh") nil t nil args)って感じです。 これでgh pr view --webコマンドを実行してPRをブラウザで開けるようになります。

次は ("m" "Merge" magh-pr-merge-transient) です。先程の magh-pr-view はコマンドでしたが magh-pr-merge-transient は別のtransient prefixです。

(transient-define-prefix magh-pr-merge-transient ()
  ["Arguments"
   ("-d" "Delete branch" "--delete-branch")]
  ["Actions"
   ("m" "Merge commit"     magh-pr-merge-commit)
   ("r" "Rebase and merge" magh-pr-merge-rebase)
   ("s" "Squash and merge" magh-pr-merge-squash)])

Argumentsではトグル可能なオプションを定義しています。 3つ目のロングオプションの部分を"--option="とすることで値をインタラクティブに聞くこともできます。 このオプションは (transient-args 'magh-pr-merge-transient) でリストとして取り出すことができます。

あとは"gh pr merge"を実行する関数を作ってやれば動きます。

(マージしたあとリストから消えて欲しいけど、すぐリフレッシュしても消えていないのでちょっと難しい…)

以上でだいたいやりたいことができました。

感想

transientを使ってみたのは初めてでしたが、非常に簡単にmagit風のツールを作ることができました。 個人的にこのghラッパーは少しずつ発展させていきたいと思っています。 楽観的には、man ghがあればあとはだいたい生成AIに任せられそうですね。

(ところで、わざわざghのラッパーを作らなくてもmagitにはリモートの操作を抽象化したforgeという拡張があるじゃん、と思われた方もいるかもしれません。が、forgeはGitHub以外のリモートも含めて汎用のAPIを使う前提であり機能に制限があること、認証情報の保存がEmacsの仕組みに乗っていてgh auth loginほど簡単でないことから見送っています。)

みなさんもtransientを使って便利な道具を作ってみてください。その際はtransient-showcaseが参考になると思います。


はてなアドベントカレンダー、明日12/24は id:SlashNephy さんの記事の予定です。お楽しみに!!!