お前らのXXXXは<ネガティブな形容詞>シリーズ で失礼します。
日頃gitをお使いの皆様におかれましては、キレイなコミットを心がけていらっしゃいますでしょうか。
私も心がけてはいますが、なかなか難しいものがあります。
参考までにこちら、最近業務で書いたプルリクエストのコミットログです。
控えめに言って汚いと思われたかと思います。
ではキレイなコミットの例を。
プルリクエストというのは、やはり先達の方に見ていただいてご指摘いただこうというものですから、
当然コミットハッシュもゾロ目等でキレイにするというのがマナーです。
では今回はこのキレイなコミットをどうやって作るのか、という話を書きます
(ショート)コミットハッシュ
コミットハッシュとは、gitのコミットごとに生成される、40桁の[0-9a-f]からなる文字列です。
お手元のリポジトリ上で git log --format=%H
を叩くとコミットハッシュの一覧が出力されるかとおもいます。
これの頭7桁をとったものがショートコミットハッシュで、先程の画像の右側に表示されていたものです。
git log --format=%h
で(ほぼ)ショートコミットハッシュ取得することができます(し、単にコミットハッシュの8文字以降を見なかったことにしても同一のものが得られます)。
ではコミットハッシュはどのように決定されるのかというと、
詳しくは
あたりをご参照いただいて、
簡単に説明すると、
「ディレクトリ構造」と「親コミット(≒直前のコミット)」と「Author」と「Committer」と「コミットメッセージ」によって決定されます。
これらのうちどれか1つでも変更があれば、コミットハッシュは再計算されます。
キレイなコミットハッシュを作る
gitはコミット後であっても過去のコミットについて情報を変更することができるのはご周知の通りです(git commit --amend
や git filter-branch
等で歴史を改ざんした経験があるかと思います)。
ということは、コミットした後、ハッシュがキレイでなければ、情報を変更し、またハッシュを再確認し、キレイでなければ.....と続けることで最終的にはキレイなコミットハッシュを持ったコミットを生成することが可能なはずです。
狙ったコミットハッシュを生成するには、このループをおよそ 16 ^ 40 回繰り返せば達成されるはずですが、ちょっとだけ現実的ではないのと、
そもそもそういう衝突が起きないように設計されているのがコミットハッシュですからこれは諦める必要があります。
参考:
How hard is it to get a git hash collision?
天下のGoogle様でも3年前にようやくハッシュが衝突する2つのファイルを生成できるようになった程度です。
狙ったハッシュを生成するのはさらに困難でしょう。
ショートコミットハッシュならどうでしょうか。16^7(≒ 2.7億) 程度であれば総当たりでなんとかなりそうな気がします。
そこで、なんとかしてくれるツールを作りました。
commit_artist
Rustで書いたCLIっぽいものです。
使い方
# cargo を持ってない人はこちらから https://www.rust-lang.org/tools/install
$ cargo install commit_artist
$ cd <直近のコミットを改ざんしたいリポジトリのディレクトリ>
$ git log -1 --format=%H
86637c3f206d228df1dc1dafa49d31b159b8a358
$ commit_artist -p 1234567
173015040 hashes calculated...
Yay! Now your new hash of the latest commit is 12345672abd92a159f3886e08951f29ee7ce0041.
$ git log -1 --format=%H
12345672abd92a159f3886e08951f29ee7ce0041
yay!
ちなみに、 git cat-file
(コミットオブジェクトを覗き見るコマンド) をするとこんなかんじです
$ git cat-file -p 12345672abd92a159f3886e08951f29ee7ce0041
tree 839ac38ae8ee7604922680774468473c33162b5c
parent eeeeeeea55ebeec4794897154720d08812304a1c
author rnitta <attinyes@gmail.com> 1582426518 +0900
committer d6dc479254c0faa95cd8ee438a6b6611ce62b1c8 <attinyes@gmail.com> 1582426518 +0900
update comments and did something
commiterの名前が怪しい文字列になっています。
仕組み
仕組みとしては、
外部コマンド git
を叩いて最近のcommitに関する種々の情報を取得し、
コミッターの名前を変更して新たにコミットハッシュを再計算し、
これをキレイなハッシュが見つかるまで繰り返す、というものです。
コミッターの名前を変更している理由は、
「ディレクトリ構造」と「親コミット(≒直前のコミット)」と「Author」と「Committer」と「コミットメッセージ」によって決定されます。
この中で改ざんしやすいものはAuthorとCommiterとコミットメッセージで、
Authorを改ざんするのはよろしくないし、
コミットメッセージは人の目に触れやすいので改ざんはよろしくないし、
Committerの名前かメールアドレスなら改ざんしてもいいだろうと。
アーキテクチャを説明した風の図がこちらです。
マルチスレッドにしているのが工夫ポイントかなと思います。
こういった終わらなければ無限にタスクを積み続けるようなプログラムだと、ワークスティーリングなランタイムを使ったasync/awaitが相性良いかなとは思ったんですが、async/awaitなーんもわからん状態に陥ったので任意のサイズのイテレーションを回す感じのマルチスレッドで実装してみました。
Issue, PR, Star! お待ちしております。
https://github.com/rnitta/commit_artist
CLI
書いたプログラムをCLIにするにあたって、
(--path とか --jobs みたいなオプションを受け取るために)
Rust製のCLIフレームワークである、 Seahorse を使っています。
ロゴがかっちょいいでお馴染みのSeahorseです。まあ僕がロゴ(アイコン)作ったんですけど。
ミニマルですし、外部クレートに依存していないし、コード量も多くないのでサクッと読めます。
https://github.com/ksk001100/seahorse
こちらもぜひ
免責
Committer nameを書き換えますし、コミットハッシュを意図的に頭数文字固定してしまおうというevilみがあるプログラムなので、業務で使って怒られてもしりません。
あと最悪リポジトリ壊れてもしりません。