新任Git管理者のための歴史改変入門

この講義について

役割としてGit管理者をしていたり、もしくは特段評価に反映されないのに他に詳しい人がいないからという理由からGit管理者みたいなことをする羽目になった人向けの履歴改変入門です。

前提

ここでの歴史改変とは、Gitリポジトリの各コミットに対して特定のファイルの除去もしくは追加、コミット情報を改変する事を指します。

歴史改変する前に

filter-branchを使用して歴史改変を実施すると、各コミットのSHAハッシュが書き換わります。 つまり、改変前後で(masterなどの)ブランチが指し示すコミットが変わってしまうため、リモートにプッシュしていないコミットがあった場合はプッシュできなくなります。

一般的な運用ではそのリポジトリを触っている関係者全員に歴史改変を実施する旨を伝え、都合の良いタイミングで作業を止めてもらい、すべてのコミットをプッシュしてから実施すべきです。

静止点を作らなくてはいけない都合上、そのリポジトリに関係する関係者が多ければ多いほどスケジュールの調整コストが増大します。

歴史改変手順

関係者とのスケジュールの調整

上記で述べた通り、歴史改変ではコミットハッシュが書き換わるためその旨を関係者全員に通知します。

また、どのタイミングのリモートの情報を使用して歴史改変をするか、歴史改変作業中に作業を止めてもらうのか、それともチェリーピックで対応してもらうのかなどを通知します。

関係者がGitに詳しくない場合は作業を止めてもらうのが一番確実です。

リモートブランチのクローン

作業に自信がない場合は、日々の作業で使用しているローカルブランチとは別に今回の作業用のローカルブランチをプルしてくることをお勧めします。

作業用のローカルブランチとは別に、インスタントなバックアップとして--bareオプション付きでクローンしておくと安心かもしれません。

歴史改変

作業用のローカルブランチを使用して歴史改変を実行します。

確認・プッシュ

改変後のコミットを確認し、意図した内容に書き換わっているか確認します。

意図した内容になっている事が確認出来たら、既存のリモートリポジトリに--forceオプション付きでプッシュするか、新規のリモートリポジトリにプッシュします。

関係者に通知

作業が完了し、リモートへのプッシュが完了したら関係者に通知します。

その際、既存のローカルブランチを削除して再度リモートからクローンする必要がある旨を合わせて伝えると良いでしょう。

--tree-filter <command>オプションについて

この講座では--tree-filterを使用します。 --tree-filterは以下の原理で動作します。

  1. .git-rewrite/tディレクトリ(デフォルトから変更しない場合)以下にコミット時点のワーキングツリーの状態を再現する
  2. <command>を実行する
  3. オリジナルのコミットと.git-rewrite/tの内容を比較し、差分を抽出しコミットする
  4. 上記の手順を指定されたブランチの全てのコミットに対して実行する

コマンド例

履歴から特定のファイルを消す場合

$ git filter-branch --tree-filter 'rm -f README_jp.md' -- HEAD

履歴にファイルを追加する場合

$ git filter-branch --tree-filter 'cp -p ~/source/orig/msg.txt .' -- HEAD

特定の人物の存在を無かったことにしたい場合

git filter-branch --env-filter '
  if test "$GIT_AUTHOR_EMAIL" = "jyuch@users.noreply.github.com"
  then
      GIT_AUTHOR_NAME=nobody
      GIT_AUTHOR_EMAIL=nobody@jyuch.dev
  fi
  if test "$GIT_COMMITTER_EMAIL" = "jyuch@users.noreply.github.com"
  then
      GIT_COMMITTER_NAME=nobody
      GIT_COMMITTER_EMAIL=nobody@jyuch.dev
  fi
' -- HEAD

おわり