分散バージョン管理システムのGitには様々なサブコマンドがありますが、その中の1つである git filter-branch を使用すると、過去のコミットを完全に無かった事にしてしまうなどの強力なコミット履歴の編集が可能となります。大きなリポジトリの特定のディレクトリ以下の内容をコミット履歴付きで別の小さなリポジトリとして取り出したり、ファイルの中に書かれていた生のパスワードを履歴の中から消去したり、というのはよく紹介される例です。
このエントリでは別の例として、コミットメッセージだけを後からまとめて修正する手順をご紹介しましょう。
元々非公開なプロジェクトとして開発を進めていたものを、公開リポジトリに移動したいとなると、やはり機密情報は完全に取り除いておく必要があります。リポジトリに格納されているファイルそのものの内容の編集方法については上記の例で解説されていますが、それ以外の場合として、コミットメッセージに書き込まれている情報を消去したいという事がたまにあります。
実際あったケースで問題になったのは、マージコミットのメッセージでした。
複数人で開発しているプロジェクトでは、自分のコミットをpushしようとするとエラーになってしまったので、一旦pullしてマージしてから再度pushする、という事がよく起こります。この時、gitが生成する既定のコミットメッセージには以下のように、pullしたリポジトリの位置が含まれてしまっています。
commit 213aa813611179b5cc4139e82f672922921e340a
Merge: a57bd32 511348d
Author: SHIMODA Hiroshi <shimoda@clear-code.com>
Date: Wed Jun 15 15:44:15 2011 +0900
Merge branch 'master' of github.com:piroor/treestyletab
この例ではgithubのリポジトリの位置が書き込まれていますが、プロジェクトの参加者全員で使用している中央リポジトリが秘密のサーバの上に置かれていた場合や、あるいは参加者の各PCの間で直接IPアドレスを指定するなどしてpullしあっていた場合には、pullしたリポジトリの位置として「192.168.1.2」のような公開される情報には相応しくない内容が出現してしまいます。
git pull --rebase としてpullすればこのような事は起こらないのですが、やってしまった物はもう仕方がありません。このようなコミット履歴がたくさんある時でも、git filter-branch を使用すると、コミットメッセージを任意の内容で書き換えた新しいリポジトリを作成する事ができます。具体的な手順は、以下の通りです。
% git clone git@internal.example.com:private-project.git
% cd private-project
% git filter-branch --msg-filter 'sed -e "s/Merge.*internal.*\$/Merge/"' -f
filter-branch サブコマンドの --msg-filter オプションには、任意のシェルコマンドを文字列として渡す事ができます。このコマンドに対しては、元のコミットメッセージが標準入力として流し込まれ、コマンドの実行結果の標準出力が新しいコミットメッセージとなります。上記の例のように sed などを使って文字列を置換すれば、あまり人目にさらしたくないコミットメッセージを削除するという事もできます(ここでは例として、「Merge」という文字列で始まり行中に「internal」という内容を含む行があれば、それをすべて単に「Merge」という文字列に置き換えています)1。
git log で編集後のコミットログを確認して、期待通りの編集結果が得られていれば、後は公開リポジトリにpushするだけです。
% git push git@public.example.com:public-project.git
以上、コミットメッセージの中に含まれた不適切な内容を書き換えた新しいリポジトリを作る手順をご紹介しました。皆さんもこの手順を使って、社内でしか共有されていなかった便利ツールなどを広く一般に公開してみてはいかがでしょうか。
-
ただ、この時実際には、古いコミットメッセージを伴う元々のコミットと、新しいコミットメッセージを伴う全く別のコミットの両方が、リポジトリには含まれた状態になっています。元々のコミットはmasterなどのブランチに紐付けられていない迷子のコミットという扱いになるため、git log では通常は表示されませんが、git log --all とオプションを指定したり、リビジョンを直接指定した場合には、古いコミットメッセージが残っている事を確認できます。この時の作業用のリポジトリの内容を新しい空の公開リポジトリにpushする場合は、このような迷子のコミットはpushされないので気にする必要はありませんが、何らかの理由でその作業用のリポジトリを(ファイルコピーなどで)そのまま外部に公開するとなると、迷子のコミットであってもリポジトリの中に残っているのは危険です。このような場合には、git gc --prune=now でゴミを掃除して迷子のコミットを完全に消去し、それらを閲覧できないようにしてしまうとよいでしょう。 ↩