Gitでファイルごとの初版を取り出す
まずは結論
Gitで、今あるファイルごとに、そのファイルの初版 (つまり最初にコミットしたときのバージョン) を取り出すには、このワンライナーが良さげなのでメモ (1行じゃないけど)。
for file in $(find . -name .git -prune -o -type f -print); do
echo $file
git checkout $(git log --format=%H $file | tail -n 1) -- $file
done
中身の説明
git log
の引数にファイルパスを指定すると、そのファイルをいじったコミットログだけが表示される。
git log
の--format
オプションは、出力するログの形式を指定するもので、%H
でコミットハッシュだけを表示させる。
コミットログは新しいもの順に表示されるので、tail -n 1
でいちばん古いもの、すなわちそのファイルが最初に追加されたときのコミットハッシュを取り出す。
git checkout
の第1引数にはブランチだけじゃなくコミットも指定できるので、上で取得したコミットを指定し、--
を挟んで対象ファイルを指定する。--
は、「このあとに書くものはオプションじゃなくて全部パスだよ」の意。
find .
は、カレントディレクトリ以下のディレクトリとファイルをすべて表示するもの。これに-type f
をつけて、ファイルだけに限定する。
ただし、そのままだと./.git
の中のファイルまで検索対象にしちゃうので、-name .git -prune
を付けて、「.git
という名前のディレクトリはそれ以上深追いしないで」とfindに指示する。-o
はorの意で、-o
の左の条件が満たされたときだけ-o
の右側を評価する。-prune
は対象がディレクトリだったらTrueを返すので、対象が.git
という名前のディレクトリじゃないときだけ-type f
に進む。-o
で条件をつなぐときは、右側に-print
もつける。これがないと、-o
の左でヒットした./.git
までパスが表示されてしまって元の木阿弥。なお、.git
を除外するにはgrep -v
で弾く方式も考えられるけど、.git
は外しつつ.gitignore
は含めるとかが意外に面倒なのと、find
の探索自体は.git
の配下もやっちゃってるのが微妙にもったいないので不採用。
なんでこんなことを?
そもそもなんで各ファイルの初版なんか取り出したくなったかというと……
1日1ファイルずつ翻訳記事を追加しているリポジトリがあって、その中の「単語メモの語義」を遡及修正するたびにコミットしてたら、1ファイルの修正履歴が何十個とかになっちゃった (リポ全体のコミット数は万に届きそうな勢い)。だけどこの場合、興味があるのは最初の版と最新版だけで、その間の修正経緯は別に要らないというか、むしろ途中でうっかりいらんとこ触ったりしたミスに気付きにくくなって邪魔。
なので、初版のセットと最新版のセットだけで、新しいリポを作って再出発した、というわけ。
※バージョンメモ
- git version 2.43.0
- find (GNU findutils) 4.9.0
- GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)