ghq list -pをRustで200倍速くする

3 min read読了の目安(約3400字

そもそもghqって?

ghqはManage remote repository clonesしてくれる君です。

今はGOPATHとか設定しなくてもGolangは上手く動くようになったようですが、昔はGOPATH配下にソースコードを置いておく必要がありました。

その名残で現在私のソースコードはという感じのディレクトリ構成で管理されています。
/Users/serinuntius/src/github.com/**

そこでそのパス通りにインストールするときに楽なのがghqです。
ghq get <repo>ってやると、良い感じのパスにcloneしてくれます。

ghq list -pとは?

ghq list -pはローカルのリポジトリ一覧をフルパスで出力してくれる君です。

/Users/serinuntius/src/github.com/hoge
/Users/serinuntius/src/github.com/fuga
/Users/serinuntius/src/github.com/piyo
/Users/serinuntius/src/github.com/poyo

パスを出力してくれるので、pecoというcliに食わせてフィルタリングして、リポジトリのディレクトリ移動に利用しています。ghqでリポジトリ管理とpeco連携で快適git生活が参考になります。

https://qiita.com/strsk/items/9151cef7e68f0746820d

手元には134のリポジトリもあるので、実行に時間がかかるようになってしまいました。

ghq version 1.1.5 (rev:HEAD)のベンチマークを回してみると

hyperfine 'ghq list -p'
Benchmark #1: ghq list -p
Time (mean ± σ):      1.185 s ±  0.334 s    [User: 1.347 s, System: 10.284 s]
Range (min … max):    1.006 s …  2.128 s    10 runs

という感じで、1秒以上時間がかかっています。リポジトリを移動するたびに実行するコマンドなので、もう少し速くしたいですね!

Rustでghq list -pっぽいことしてくれるツールを作った

https://github.com/serinuntius/pacifica

今Rustの勉強をちょうどしていて、やってこることは正直ただのシェルでもできちゃうぐらい簡単なものなので、存在価値はほとんどありませんw

なんとなく、素振りしたかっただけですw

ghq list -p では色々重複チェックとか、バージョン管理システムを使っているディレクトリか等チェックしているようですが、自分のメインの用途では少しオーバースペックだと感じました。

とりあえず、最速で動かしたいので、指定のディレクトリを掘っていくだけのツールを書きました!
並列処理もしていないシンプルな処理ですが、とりあえず191倍速くなりました。

これはRustのおかげで速くなったわけではないと重々承知しております。
ghqの作者様たちには感謝しております。

Rust学習2日目に書いたので、色々と至らない点はあると思いますがソースコードです。

use std::env;
use std::path::PathBuf;
use std::io::{stdout, BufWriter, Write};

fn base_path() -> PathBuf {
    match env::var("PACIFICA_PATH") {
        Ok(path) => PathBuf::from(path),
        Err(_) => PathBuf::from(env::var("HOME").expect("failed to read $HOME. You have to set PACIFICA_PATH.") + "/src"),
    }
}

fn walk_dir(path: PathBuf, depth: i8) {
    // buffering
    let out = stdout();
    let mut out = BufWriter::new(out.lock());

    let paths = path.read_dir().expect("failed to read dir");

    for p in paths {
        let pb = p.unwrap().path();
        let path_string = pb.display().to_string();

        if !pb.is_dir() {
            continue;
        }

        if depth - 1 >= 0 {
            walk_dir(pb, depth - 1)
        } else if !path_string.split("/").last().expect("failed to path_string.split(\"/\").last()").starts_with(".") {
            // prevent something like a .git coming in.
            write!(out, "{}\n", path_string).expect("failed to write stdout");
        }
    }
}

fn main() {
    let base_path = base_path();

    walk_dir(base_path, 2);
}

ベンチマーク

hyperfine './target/release/pacifica'
Benchmark #1: ./target/release/pacifica
  Time (mean ± σ):       6.2 ms ±   0.5 ms    [User: 1.5 ms, System: 3.6 ms]
  Range (min … max):     5.4 ms …   8.3 ms    325 runs
  
hyperfine 'ghq list -p'
Benchmark #1: ghq list -p
Time (mean ± σ):      1.185 s ±  0.334 s    [User: 1.347 s, System: 10.284 s]
Range (min … max):    1.006 s …  2.128 s    10 runs

6.2msなので体感的には一瞬で全くストレスはありません。

pecoでディレクトリ移動するやつ

ctrl + ]でpecoで移動できます。

function peco-src () {
  local selected_dir=$(pacifica | peco --query "$LBUFFER")
  if [ -n "$selected_dir" ]; then
    BUFFER="cd ${selected_dir}"
    zle accept-line
  fi
  zle clear-screen
}
zle -N peco-src
bindkey '^]' peco-src

まとめ

普段使っているツールを自分の用途に合わせて高速にするのは楽しいですね。not for meの人もいるとは思いますが、遅くて困っている人は使ってみてはいかがでしょうか?