詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

go-cli: ドキュメントとともにコマンドラインツールを作る

hub-pr を作るとき、(ghq などで使っていた)codegangsta/cli ではなく新しいライブラリを試してみようと思って mitchellh/cli を使ってみたけど、何かしっくりこないものがあったので、せっかくだし、と自作してみた。今回の要件は以下のとおり。

  • gogit のように)サブコマンドがある
  • コマンドラインオプションの解析には標準の flag パッケージをつかう
  • コマンドを追加するのが面倒でない

で、go-cliドキュメント)。

コマンドの実装

hub-pr のソースコードが一番のサンプルだけど、コマンドは以下のような関数で表現される。

func doCheckout(flags *flag.FlagSet, args []string) error {

各コマンドの実装は初期化された *flag.FlagSet とコマンドの引数(プログラムの第1引数がコマンド名になるので os.Args[2:])を受けとる。フラグの管理と引数の処理をおこなうのはそれぞれの関数の責任。まったくおこなわなくてもよい。

    flags.StringVar(&tmpl, "f", tmpl, "branch name format")
    flags.Parse(args)

また、各関数は error を返すことで異常終了するよう cli に指示できる。とくに cli.ErrUsage は使用方法がおかしいことを示し、flags を利用しつつ呼び出し側でいい感じにエラーを表示してくれる。

% hub-pr checkout
Usage: checkout [-f TEMPLATE] PULL_REQUEST_NUMBER

Checks out a branch corresponding to given pull request number.  The branch
name is based on the template, which defaults to
'#{{.Number}}-{{.Head.Repo.Owner.Login}}/{{.Head.Ref}}'.

Options:
  -f="#{{.Number}}-{{.Head.Repo.Owner.Login}}/{{.Head.Ref}}": branch name format

コマンドの登録・実行

コマンドを登録し、実行するには以下のようにする。

    cli.Use(
        &cli.Command{
            Name:   "checkout",
            Action: doCheckout,
            Short:  "Checkout a branch for a pull request",
            Long:   "checkout [-f TEMPLATE] ...",
        },
    )
    cli.Run(os.Args[1:])

で、この Name やら Long やら、ヘルプを書くのが面倒なんですよ……。とくに文字列リテラルとして書かなければならないあたりになんとなくストレスを感じる。ドキュメントなのだから普通に書きたい。そこでコメントを使う。

// +command checkout - Checkout a branch for a pull request
//
//   checkout [-f TEMPLATE] PULL_REQUEST_NUMBER
//
// Checks out a branch corresponding to given pull request
// ...
func doCheckout(flags *flag.FlagSet, args []string) error {

以上のように、コマンドに対応する関数のドキュメントに "+command" で始まるヘルプを含めてやるというルールにする。普通の文字列リテラルにくらべて、コメントでドキュメントを書くほうがメンテしやすいしコードを書くときにも参照しやすい。はず。

コメントからコマンドの登録コードを生成する

このコメントどう使うのって話だけど、ここから先に挙げたような cli.Use() の形のコードを生成するための(雑な) API を go-cli/gen として提供している。

hub-pr では _tools/gen_commands.go から利用している。ドキュメントを更新したら go run _tools/gen_commands.go すれば commands.go が自動生成される。実際には go generatable にしている。プロジェクトにあわせて自由に使えるように、ツールの形では提供していない。

go-cli

オレ流 Pull Request 作業フロー

チームで作業する同じリポジトリの中で Pull Request を送り合うのではなく、オープンソースプロジェクトに外部から PR がやってくる場合の話です。

最近のフロー

送られてきた PR に対しては、大まかには仕様の話、実装方針の話、具体的な実装の話を詰めながらマージできるように持っていくわけだけれど、それがほとんど満足いく状態になっていてマージしたいと思うタイミングになっても、変数の名前付けだとか、ちょっとした処理の書き方だとかで、相手にお願いするよりは自分で手を加えてからマージした方が手っ取り早いことがある。そういう時は PR 元のブランチを手元にチェックアウトして、そのブランチを自分の変更で進めた上で master にマージするようにすると、push 時に PR も閉じられて便利です。

motemen/lgtm.sh#1 の例。分かりにくいれど、PR にさらに 1 コミット足してからマージしてる。

この際コードは fork された外部のリポジトリにあるから、たんに git checkout すればいいわけではなく予め git fetch なりする必要がある。

で、最近いろいろ試行錯誤した結果、以下のようにするとよさそうだった。

1. フォークされたリポジトリを、そのオーナー名で remote に登録する

git remote add -f jwerle https://github.com/jwerle/lgtm.sh.git

-f オプションで、登録後 git fetch する。

2. PR 元のリモートブランチを、ローカルにチェックアウトする

git checkout -b '#1-jwerle/master' --track remotes/jwerle/master

この際 #xxx をブランチ名に入れておくと、コミットメッセージなどにブランチ名が入った時に自動的にリンクになって便利(ありがたいことに # はブランチ名に使える)。また、このブランチで git pull すると、PR が更新されたときに最新のコードを取得できる。

3. ローカルにチェックアウトされたブランチの push 先を自分のリポジトリに変更

git config --local 'branch.#1-jwerle/master.pushremote' origin

これで、このブランチを変更したときに、自分のリポジトリに push することができる。この設定後も依然として git pull は相手のリポジトリから行われる。

hub-pr

一度以上のようにしてしまうと結構便利なわけだけど、セットアップに少し面倒がある。最近 PR を受け取ることが重なったこともあり、このへんの作業はもうフローが決まってるのでコマンド化することにした。ホントはちょっとしたシェルスクリプトに収めるつもりだったのだけど、なぜか色気を出してしまった……。yak shaving だけが人生だ。

motemen/hub-pr · GitHub

go get github.com/motemen/hub-pr で入手できます。

使い方

hub-pr checkout PULL_REQUEST_NUMBER

PR 番号を与えると、以上に書いたフローでブランチを作ってくれる。zsh 用の補完を用意してるので以外と簡単なのです。

f:id:motemen:20150324003457g:plain

あとは普通にマージしてやればいいのだけど、手元でマージするとあの GitHub おなじみのマージメッセージにならないので、それと同じものを生成するコマンドも用意しておいた。

hub-pr merge BRANCH

これであたかもマージボタンを押したかのようなコミットメッセージでもってマージされます。なんか便利ですね。ほかにも hub-pr browse っていう便利なのもありますが、オマケです。コンソールから PR やイシューを扱うなら、ghi ってのが圧倒的に便利。


このフローで作業したいと思う人がいるかどうかは分かりませんが……、どうぞご利用下さい。verbose output 的なオプションがないのでわりとブラックボックス感が高い。

Go で GitHub 用のツールを書く時は github.com/github/hub 以下のパッケージを使うとなかなか快適に行えるようでした。まあそりゃそうか。

GitHub実践入門 ~Pull Requestによる開発の変革 (WEB+DB PRESS plus)

GitHub実践入門 ~Pull Requestによる開発の変革 (WEB+DB PRESS plus)

秒速でLGTMするコマンド

LGTM するときはよさげなアニメーション GIF を探し出してきて lgtm.herokuapp.com にかけるのがデファクト[要出典]だけどこの「よさげなアニメーション GIF を探し出す」というのがくせ者で、大量のアニメーション GIF をブラウザで開くと CPU パワーを浪費するし選択にかける人的な労力もばかにならない。エコではない。そこで Tumblr のランダムな画像を LGTM 化するコマンドラインツールを書いた。その名も lgtm.sh だ。Tumblr には /random というエンドポイントがあるのでこれを利用して特定の Tumblr ブログ群からランダムに画像 URL を得ている。

./lgtm.sh -m | pbcopy

-m オプションをつけると Markdown フォーマットで出力する。つけない場合は画像 URL のみ。

LGTM

/random へのアクセスには少し時間がかかるのだけど、これを軽減するため、コマンドの毎実行時に同期的にアクセスするのではなく、実行後にバックグラウンドで HTTP GET してキャッシュ(次回の結果)を生成するようにしている。秒速たるゆえんだ。

実装は簡単なシェルスクリプトで、sites 変数を好きな Tumblr のリストにすることで生成される画像をカスタマイズできる。デフォルトではパブリックドメインのアニメーション GIF を利用するようになっているので、自分好みに編集してうまく活用してもらいたい。

motemen/lgtm.sh · GitHub

はてなで一緒に働きませんか?