Emacs から Github の issues やプルリクエストが見える magithub というパッケージがあったんですが magit の派生プロジェクト forge に組み込まれる予定だと書いてありました。そこで forge というパッケージについて調べてみました
2019/10/30追記: emacs forge は活発に更新されています。本家ドキュメントすら、最新仕様にそぐわない場合があり、翻訳してもメリットがあまりないと感じました。そのため、すみませんが、この記事は途中のまま終了とします。forge の使い方については別途記事を準備するつもりなのでお許しください。
1. Introduction
Forge は Magit や Emacs でくつろいだまま、気軽に Git forges (Github や Gitlab)を使うことを可能にします。
Forge は issue や プルリクエスト やその他のデータを、使っている forge の APIから取得し、ローカルデータベースに保存します。加えて Git を使う プルリクエスト references も取得します。Forge はこのデータを使った様々な機能を実装していますが、データベースと プルリクエスト references はサードパーティのパッケージからも使うことができます。
2. Supported Forges and Hosts
現在 Forge は二つの forge をサポートしており、三つ以上の forges を部分的にサポートしています。加えて semi-forges を四つサポートしています。もっと多くの forges と semi-forges をサポートが追加されうるし、実際されていくるでしょう。
forges と semi-forges の両方は Git リポジトリのためのウェブインターフェースを提供しています。Forge は付加的に プルリクエスト と issues と、それらの作成、そして使用可能な API で得られるその他の情報をサポートします。
ある forge が部分的サポートであるという時、それは API を必要としない機能だけが実装されるという意味です。言い換えると、その forge は semi-forge としてのみサポートされます。
ホストはある forge の部分的なインスタンスです。たとえば2つのホスト https://gitlab.com と https://salsa.debian.org はどちらも Gitlab forge のインスタンスです。Forge は独創性をよく知られたいくつかのホストをサポートします。さらに、オプション forge-alist にエントリを追加することで、簡単にサポートを追加することもできます。
詳細は、以下で言及した注意だけでなく Getting Started を見てください。
略
3. Getting Started
Forge を使い始めてみるのはすごく簡単ですが、少し注意するべき事があります。
- Forge は forge の API へアクセスするのに the Ghub パッケージを使います。このパッケージには、Github API トークンを生成し保存するのを簡単にするセットアップウィザードがついています。残念ながら、他の forges には同じことをすることができません。以前には、何人かのユーザーが二要素認証を使っているときに Github に対しても失敗しました。Token Creation の節を見てください。
- 取得した情報はデータベースに保存されます。そのデータベースのテーブルスキーマは、まだ最終型ではありません。完成するまでは、時折削除しなければいけないかもしれません。それは特に巨大な操作ではありません。なぜなら現在、データベースは再取得できないようなどの情報も含んでいないからです。Initial Pull の節を見てください。
- 取得 (Fetching) は、最後にチェックしてからの変更点リストを API に依頼できる、という仮定の下で実装されました。残念ながら APIs はバグがないわけではないので、常にその通りであるわけではありません。この issue (訳注:原文にはリンクが有りましたが、リンク先は見れなくなってるようなので省略)のように、(クローズドなソフトウェアでは)アドレスを取得するのには何年もかかることがあるので、それを理由にして私は Forge のリリースを遅らせることはありません。疑いを感じるならば、更新を確かめるためにコマンド
forge-pull-pullreq
を使って、個別の プルリクエスト を再取得します。 - その他、forge 固有の注意については Supported Forges and Hosts の節(訳注:省略してしまったので本記事にはない。原文参照)で言及しました。
Magit をロードするとき Forge は自動的にロードされるわけではありません。初期化ファイルに、以下の内容を追加することで、これを解決します。
(with-eval-after-load 'magit
(require 'forge))
あるいは use-package
を使っているなら以下のようにします。
(use-package forge
:after magit)
3.1 Initial Pull
あるリポジトリで Forge を使い始めるには、 Magit でそのリポジトリの status バッファを開き、F y
(forge-pull
) を実行します。
https://github.com にあるどれかのリポジトリに対して、初めてそれを実行するときは、API トークンを作成するプロセスを通じてガイドされるでしょう。他の forges の場合や、同様に他の Github インスタンスの場合は、そうする前に他の追加のセットアップが必要です。Token Creation の節を見てください。
リポジトリ内の初回の forge-pull
が実行されたとき、リポジトリのエントリはデータベースに追加されます。そして、新しい値が Git の変数 remote.<remote>.fetch
に追加されます。これはすべての プルリクエスト を取得するものです。(Github での +refs/pull/*/head:refs/pullreqs/*
)
forge-pull
は、そしてトピックを取得して、forge の API を使ってその他の情報を取得し、さらに Git を使って プルリクエスト のリファレンスを取得します。
初回の fetch は、時間がかかるかもしれませんが、そのほとんどは非同期的に実行されます。データベースに情報を保存することは非同期的に実行されます。しかしながら、最後には注意を引くハングがあるかもしれません。その後の fetch はより早くなります。
Github から issues を取得するのは、他の forge から取得するよりも速いです。なぜなら、わずかな量の GraphQL リクエストを作るのは、どんな REST リクエストを作るよりも速いからです。
3.2 Token Creation
Forge はサポートしている Git forges の API にアクセスするのに、Ghub パッケージを使います。Ghub はセットアップウィザードを持っています。ウィザードはユーザに対して Github.com の API トークン生成のプロセスをガイドします。 Github Enterprize インスタンスにアクセスしているときは、ウィザードを使う前に、いくつかのマニュアルセットアップが必要です。他の forges は API を使うためのトークン生成をサポートしていません。このような forge にアクセスする前には、それぞれのウェブのインターフェースを使ってトークンを生成する必要があります。
トークン生成についてもっと学ぶには Ghub のマニュアルを調べてください。特に ghub の Getting Startedを見てください。
Ghub は与えられたローカルリポジトリを forge 上のリポジトリに関連付けしません。Forge パッケージはそれ自身がこれに気を配ります。こうすることによって、Git の変数 ghub.host
を無視して、他の FORGE.host
変数を Ghub が使うことができます。(ただし gihub.user
と、ユーザーを指定するために使う他の変数は、尊重されます。)Forge は、どのリモートが upstream リポジトリに関連付けされるかを最初に決めることによって、ローカルリポジトリと、forge のリポジトリを関連付けします。そしてそれを forge-alist
で調べます。
もしもリモートがただ一つしか存在しないならば、そのとき Forge は無条件にそれを使います。もし複数のリモートが存在するならば、そのときはその名前をベースにして選択されるでしょう。
upstream リモートを origin
と名付ける慣習があります。もしあなたが、この慣習に従うなら、何もしなくてよいです。リモートはその名前で自動的に使われます。他のどんなリモートが存在していても気にしません。
もし、あなたがその習慣に従わないなら、Git の変数 forge.remote
設定することによって upstream リモートの名前を Forge に知らせなくてはいけません。この変数がセットされていて、存在しているなら Forge はその名前を使ってリモートを使い、変数が未定義の場合には origin
と同じ方法を使います。言い換えると、もし選んだ名前のリモートが存在しないならば、失敗にせず origin
を試します。
一度 upstream のリモートが決定されると、Forge はホストの一部の URL をキーとして forge-alist を調べます。たとえば git@github.com:magit/forge.git に対するキーは github.com です。
4. Usage
一度、情報がリポジトリの forge から取得されると、Forge は二つのセクション "Pull requests" と "Issues" を Magit のステータスバッファに追加します。ポイントがこれらのセクションの中にあると、いくつかの Forge のコマンドがバインドされますが、そうでない場所の Magit のステータスバッファや Magit の一時的なコマンドから使うことができます。
(forge-dispatch
)
このプレフィックスコマンドはどの Magit バッファの中でも使うことができます。また、いくつかのForgeコマンド
へのアクセスが提供されます。ほとんどのコマンドは、いつでもバインドされますが、いくつかはそうではありません。利用可能なコマンドについては以下のセクションを見てください。
4.1 Pulling
forge のデータを取得するコマンドは、Git データを取得するのに使われる一時的なプレフィックスコマンド (magit-fetch
または f
) から使うことができます。オプション magit-pull-or-fetch
がnilではない時には、 magit-pull
(F
) からも使うことができます。
f y
(forge-pull
)
このコマンドは forge の API を使って、現在のリポジトリについてのトピックスと、その他の情報を取得します。そして、取得した情報をデータベースに保存します。同じ forge ホストから、すべてのリポジトリに対する通知も取得します。(現在は Github 限定です。)最後に、Gitを使ってプルリクエスト リファレンスを取得します。
与えられたリポジトリで、初めてこのコマンドを使ったあとは、そのリポジトリのステータスバッファで常に プルリクエストs と issues を一覧表示します。Initial Pullの節を見てください。
f Y
(forge-pull-nortifications
)
このコマンドは forge の API を使ってすべての通知を forge から取得します。現在のリポジトリに対する通知に限らないものも含まれています。
すべての通知を取得すると、関連するトピックも取得します。もし、すべてのトピックの取得をしていない時でも、それぞれのリポジトリに対してそうします。(forge-pull
を使います)しかし、トピックに "uninitialized" リポジトリのようにステータスバッファに表示させる原因にはなりません。
forge の API からどのようにデータを取得するかというと、Git のデータを取得するときと同じ方法で行っています。もしあなたがこのワークが終わったかどうか見たい場合は、明示的に実行できます。
これは、ランダムなインターバルで Forge が自分自身によって取得する挙動よりも、より破壊的でなく、より信頼性があり、より理解しやすいです。利用可能な最新のデータを期待しているなら、あなたが時折コマンドを実行することを意味しています。そのときは、最初のプルを中止する必要があります。同じことが Git にも起こりえます。たとえば、あなたが、まだ実際にプルしていないことを知っているブランチをマージするときがそうです。
M-x forge-pull-pullreq
(forge-pull-pullreq
)
このコマンドは forge の API を使って一個の プルリクエスト を取得し、データベースに保存します。
通常は、一個の プルリクエスト をプルしたい場面はないでしょう。しかし、Github API のバグが原因でこのようにしなければならない場面がああるかもしれません。
フェッチは API が最後にチェック以来の変更点の一覧を要求できるという仮定のもとに実装されています。残念ながら、APIは、バグがないわけではなく、常にそのケースであるとはいえません。この issue (訳注:原文にはリンクが有りましたが、リンク先は見れなくなってるようなので省略)のように、(クローズドなソフトウェアでは)アドレスを取得するのには何年もかかることがあるので、それを理由にして私は Forge のリリースを遅らせることはありません。疑わしいときには、更新を確かめるためにこのコマンドを使って、個別の プルリクエスト を再取得します。
4.2 Branching
Forge は、プルリクエストからブランチやワークツリーを作成したり、チェックアウトしたりするコマンドを提供します。それらのコマンドは、同じ transient prefix コマンドから利用できます。suffix コマンドは、より一般的な方法で、ブランチとワークツリーを作成したりチェックアウトしたりするために使われます。(magit-branch
上で b
または magit-worktree
上で %
)
b Y
(forge-branch-pullreq
)
このコマンドはプルリクエストから新しいブランチを生成して設定します。必要なら、新しいリモートを生成し設定します。
ローカルブランチの名前は、マージすることを求められているリモートブランチの名前と同じです。ただし、コントリビューターは、プルリクエストを開く前のブランチにわざわざ適切な名前をつけないでしょう。一番ありそうなケースは、あなたが "fork/master" を "origin/master" へマージすることを求められているときです。このようなケースでは、ローカルブランチは "pr-N" と名付けられるでしょう。N
はプルリクエストの番号です。
このコマンドはこれらの変数を常にセットします。
-
branch.<name>.pullRequest
には、プルリクエスト番号がセットされます。 -
branch.<name>.pullRequestRemote
には、プルリクエストのブランチがある場所のリモートがセットされます。 -
branch.<name>.pushRemote
には、branch.<name>.pullRequestRemote
と同じリモートがセットされます。ただし、それが利用できるときだけです。そうでないときは upstream リモートがセットされます。 -
branch.<name>.description
には、プルリクエストのタイトルがセットされます。 -
branch.<name>.rebase
には、true
がセットされます。なぜなら、プルリクエストの中にはマージコミットがないはずだからです。
このコマンドは作成したローカルブランチの upstream と push-remote も設定します。
プルリクエストが開かれたブランチに対するブランチは、常にupstreamとして使われます。このことは、"Unmerged into origin/maseter" のようなタイトルのセクションで、どのコミットがマージを求められているのか見やすくしてくれます。
他のブランチを作るコマンドのように、オプション magit-branch-prefer-remote-upstream
に依存します。このオプションは、リモートブランチ自身または、それぞれのローカルブランチが upstream として使われているかどうかのオプションです。そのため、このセクションは "Unmerged into master" のようなタイトルになることもあります。
必要かつ、可能な時には、リモートのプルリクエストブランチは push-target として使われるように設定できます。このことは、こんなタイトルのセクション "Unpulled from origin/new-feature" や "Unpulled from fork/new-feature" で、最後にレビューしてからコントリビューターによって何が変更されたのかを見やすくしてくれます。
- もしプルリクエストブランチが upstream リポジトリにあるなら、おそらくあなたは
remote.pushDefault
をそのリポジトリにセットしたはずです。何人かのユーザは、upstream へのプッシュアクセスを持っていても、その変数を個人的なフォークにセットすることを好みます。そのためbranch.<name>.pushRemote
はどんな方針でもセットできるようになっています。 - もしプルリクエストブランチがフォークの中にあるなら、あなたは普通にそのブランチをプッシュすることができます。なぜなら、Github はデフォルトで、プルリクエストの受け取り人が、リモートのプルリクエストブランチへプッシュすることを許可しているからです。それはフォークの中にあったとしてもそうです。コントリビューターは、明示的にこれを無効にしてもかまいません。