まずは結論

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)