ghqを使ったローカルリポジトリの統一的・効率的な管理について
GitなどのVCSからcloneしたローカルリポジトリをどう管理するのがいい感じなのか、よくわからない。なんとなく自己流でやっているが、もっといい方法を知りたい。
tl;dr - ディレクトリレイアウトをgolangの作法に合わせ、すべてのリモートリポジトリをghqを使ってcloneし、percolを使って簡単に検索できるようにしましょう。
追記: いまならpercolの代わりにpecoというツールを使うのもよいでしょう。というか、僕はそうしています。設定方法はこのエントリとほぼ同様の内容でいけると思います。
背景
そんな課題を抱えつつも、特になにかをするわけでもなく日々暮らしていた折、Rebuild: 42: When in Golang, Do as the Gophers Do (lestrrat)で@lestrratさんが、Goのお作法に、他の言語のリポジトリも含め、すべてあわせるようにしたとおっしゃっていて、なるほど!と思ったのであった。
Goのお作法
以下、「Goのお作法」を知らない人向けの簡単な解説。
Goのディレクトリレイアウト
Goには、外部のコードを以下のようにして取得するユーティリティが付属している。
$ go get github.com/motemen/ghq
この結果、ローカルリポジトリが、$GOPATH/src
というディレクトリ以下へ、main
パッケージがあればビルドされたコマンドとして$GOPATH/bin
以下へ、次の通りに配置される。
$GOPATH |-- src/ | `-- github.com/ | `-- motemen/ | `-- ghq/ |-- bin/ `-- ghq
これが、Goのディレクトリレイアウトに関するお作法である。簡単ですね。
パッケージのインポート
このお作法は、Goのコードを書く際にも意識する必要がある。ライブラリを使う場合、以下のように書く。
import "github.com/kentaro/delta"
この指定により、$GOPATH/src/github.com/kentaro/delta
直下にあるパッケージがインポートされる。
また、この指定は、サブディレクトリ以下に配置されたパッケージをインポートする場合にも使われる。たとえば以下のような配置のプロジェクトがあり、
$GOPATH |-- src/ `-- github.com/ `-- motemen/ `-- ghq/ |-- main.go `-- utils/ `-- log.go
いま、main.goを書いているとする。utils/log.go
を使おうとする場合、単にimport utils
とするのはダメで、正しくは
import "github.com/motemen/ghq/utils"
とする必要がある。そして、その際はもちろん、$GOPATH
以下からファイル名が解決される。
「Goのお作法」のインプリケーション
一般に、外部ライブラリやツールとして入れたソースコードと、自分のワークスペースは分けるものだ。たとえばCPANやrubygemsなどでも、普通に運用する分には、そうなるだろう。
Goにおいては、上記のような事情により、外部ライブラリと自分の開発用ワークスペースとを区別しない方が便利である。ある意味、大胆な選択といえるが、いったん慣れてしまえば、最初からこれでよかったのでは?というか、言語にかかわりなく、これでいいのでは?という気になる。
解決
では、Goのお作法に合わせようということになったところで、そのお作法通りのディレクトリ構造を、その都度手動で作るのは面倒だ。そこで、id:motemen作のghqを使う。使い方については、リンク先を読んでもらえばよいだろう。
さらに、我々としては上記のGoのお作法に合わせたいのだから、どこにcloneするかについてもきちんと設定する必要がある。これはどこでもいいのだけど、シンプルさを追求するため、以下のように、$GOPATH
と$HOME
とを同じものにすることにした。
export GOPATH=$HOME
その上で、先述の通り、Goはgo get
した結果を$GOPATH/src
以下に配置するので、ghqの設定を、以下の通り.gitconfig
に追加する。
[ghq] root = ~/src
これで、ghq get
したものも、go get
したものと同様のディレクトリに配置されることになる。また、ghqをGitHub Enterpriseに対応させる私の送ったプルリクエストも採用されたので、プライベート/パブリックなもの、github.com/GH:Eとを問わず、すべてのリポジトリを同じようにghqを用いて扱うことができる。
gem-src
ところで、gem-srcという、@amatsudaさん作成のgemがある。これは、gem install
をフックして、そのライブラリのgitリポジトリを自動的にcloneしてくれるというもの。
これに関しても、上記と同じようにghqを使って、同じディレクトリ構造でcloneしておくようにしたら便利ではないか。そういうわけで、gem-srcにプルリクエストを送ったり、amatsudaさんによる改良を経て、以下のような設定を.gemrc
に入れておくと、ghqを使ってcloneするようになった。
.gemrc:
gemsrc_use_ghq: true
その後、ライブラリをgem install
すると、以下のようになる。
$ gem i glint Fetching: glint-0.0.2.gem (100%) /Users/usr0600239/bin/ghq clone http://github.com/kentaro/glint -> /Users/usr0600239/src/github.com/kentaro/glint git clone http://github.com/kentaro/glint /Users/usr0600239/src/github.com/kentaro/glint Cloning into '/Users/usr0600239/src/github.com/kentaro/glint'... remote: Reusing existing pack: 185, done. remote: Total 185 (delta 0), reused 0 (delta 0) Receiving objects: 100% (185/185), 20.76 KiB, done. Resolving deltas: 100% (61/61), done. Successfully installed glint-0.0.2 1 gem installed
clone先の管理
このようにして、なんでもかんでもこの方法に従ってcloneしていくと、$GOPATH/src
以下に、大量のディレクトリが格納されることになるだろう。そして、なにがそこにあるのかがどんどんwかりにくくなっていく。
これに対処するにはいろんな方法があるが、ここではpercolを使う。percolはまさにUNIXライクなツールで、標準入力から入ってきた行区切りの入力に対するフィルタUIを提供する。これを使って、ghqで管理しているリポジトリのディレクトリに、簡単にcd
できるようにする。
zshの場合、このような関数を用意しておく。
function percol-src () { local selected_dir=$(ghq list --full-path | percol --query "$LBUFFER") if [ -n "$selected_dir" ]; then BUFFER="cd ${selected_dir}" zle accept-line fi zle clear-screen } zle -N percol-src
んでもって、たとえば^s
でこの関数が起動するようにしておく。
bindkey '^S' percol-src
動作の様子は以下。
応用
@lestrratさんに、以下のようなのも便利と教えてもらいました。
$ godoc $(ghq list --full-path | percol) | $PAGER
実行すると、ghqで管理されてるディレクトリからフィルターして絞り込んだ結果をgodocにわたすことで、効率よくGoのライブラリのAPIドキュメントをひけます。
結論
gitでもhgでも、プライベートでもパブリックでも、もはや同じ、統一されたディレクトリレイアウトを採用できる。そして、以上のように、そのためのツールも整った。これで私たちは、迷うことなく、コードを書いていけるだろう。