Rust で git cat-file -p コマンドを作る

晴れて無職となったので、今後はもうちょい積極的に更新していこうと思う。

これは何

https://engineering.mercari.com/blog/entry/2015-09-14-175300/
こちらの記事で解説されていることをRustとGitの仕組みの勉強を兼ねて試してみた。

方法

zlib圧縮されたファイルを解凍する。

解説

今回したこと

リポジトリこちら

$ cargo run .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad 
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/git_cat_file_p .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad`
hello world

$ cargo run .git/objects/a4/940ec3db4cd24542203a9447c4259c96294c09 | nkf -w
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/git_cat_file_p .git/objects/a4/940ec3db4cd24542203a9447c4259c96294c09`
坊っちゃん
夏目漱石

-------------------------------------------------------
【テキスト中に現れる記号について】
~~~~~
省略
~~~~~

こんな感じで.git/objects/ 配下のファイルをzlib解凍して、git cat-file -p <hash 値> と同じ結果になるようにした。

コード

コードはDQNEOさんの記事で使われてるC言語のものをRustで書き直したものです。 DONEQさん、奥村先生、ありがとうございました!

use libflate::zlib::Decoder;
use std::{
    fs::File,
    io::{self, Cursor, Read},
    str,
};

fn usage() {
    println!("Usage: ");
    println!("git_cat_file_p blob_file");
}

fn decompress(mut file: File) -> Result<String, io::Error> {
    let mut buf = Vec::new();
    file.read_to_end(&mut buf).unwrap();

    let mut decoder = Decoder::new(Cursor::new(&buf)).unwrap();
    let mut decoded_data = Vec::new();
    decoder.read_to_end(&mut decoded_data).unwrap();

    unsafe {
        let str_data = String::from_utf8_unchecked(decoded_data);
        Ok(str_data)
    }
}

fn format(decoded: &str) -> &str {
    decoded.trim_end().split('\u{0}').collect::<Vec<_>>()[1]
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() == 1 {
        usage();
        std::process::exit(1)
    }

    let f = File::open(&args[1]);

    let file = match f {
        Ok(file) => file,
        Err(error) => panic!("{}: Can't open: {}", error, args[1]),
    };

    let decoded = decompress(file).unwrap();
    let formatted = format(&decoded);
    println!("{}", formatted);
}

ただファイルをオープンして、それをzlib解凍して、コンテンツ本体部分を取り出して出力するだけというシンプルなものです。 String::from_utf8でなく、String::from_utf8_uncheckedを使っているのは、Shift_JISのときにエラーになるため。(坊っちゃんのファイルがShift_JIS)

改めて見ると、コンテンツ本体を取り出す方法format 関数のところをもっとスマートにできたらいいなあって感じですね。

参考記事

大変参考になりました。ありがとうございました。