gitリポジトリを公開するための☆過去書き換え☆テクニック

また時間操作か!

githubにリポジトリ公開したいお…でも

       ____ 
     /      \ 
   /  _ノ  ヽ、_  \ 
  / o゚((●)) ((●))゚o \  ほんとはgithubで公開したいんだお…
  |     (__人__)    | 
  \     ` ⌒´     / 

       ____ 
     /      \ 
   /  _ノ  ヽ、_  \ 
  /  o゚⌒   ⌒゚o  \  でもMySQLのパスワードとか
  |     (__人__)    |   公開できない情報が入ってるんだお…
  \     ` ⌒´     / 

       ____ 
     /⌒  ⌒\ 
   /( ●)  (●)\ 
  /::::::⌒(__人__)⌒::::: \   だから過去を書き換えるお!
  |     |r┬-|     | 
  \      `ー'´     /

 と、いうわけで、書き換えです。

githubにソースコードを置きたい…でも公開できない情報が…。ありそうなのはこんなケース?

  • MySQLのパスワードが書いてある…
  • 完全に非公開で遊ぶために書いてたから著作権上アレゲなコンテンツが…
  • 残念な感じのコミットログが…

今更その部分を隠してコミットしたって、昔のコミットログを辿られたら意味ないじゃない!んじゃあ、修正済みのやつだけコミットログ無しで公開?もっと意味ないじゃん

そこで過去の書き換えです。gitはもうぶっちゃけ何でもできるので、過去のコミットを再度やりなおして全部書き換える事ができます。以上のケースについて、修正してみましょう。

 MySQLのパスワードが書いてある…

こんなサンプルリポジトリを考えてみましょう(github)。

ファイルの一部分に公開したくない情報があるケースです。たとえばパスワード。ためしにこんなファイルを考えてみましょう。

// 以下のコードはあくまでサンプルであり、何の意味もありません。

var db = /* なんか初期化処理 */ null;

db.init({password: test}); /* パスワードを追加しちゃった! */
// データベースに何か値を突っ込む
db.save({id: 1, name: "ちさ"});
db.load(); // 何かロード処理的なもの
db.close();

db.initの行でパスワードをハードコーディングしてしまいました。公開するのには問題があるかもしれません。繰り返しますが、この状態からパスワードを隠してコミットしても過去のファイルはそのままなので、意味がありません。

さて、git logはこんな感じです。

commit e236c0d55d0d7fc4744dd5f7edc34b9c22d8cada
Author: psi <ledyba@users.sourceforge.jp>
Date:   Fri Feb 22 11:05:50 2013 +0900

    ロード処理的なものを追加した(という想定)

commit e27ea6a1cf49021b9686c7e757fbf9adf60afb3f
Author: psi <ledyba@users.sourceforge.jp>
Date:   Fri Feb 22 11:05:10 2013 +0900

    パスワードを追加しちゃったコミット。取り消したい。

commit 7c132ae20f6c1db1bff876e8d0626d41e07a8294
Author: psi <ledyba@users.sourceforge.jp>
Date:   Fri Feb 22 11:04:30 2013 +0900

    最初のコミットです。

この「パスワードを追加しちゃった」コミットで、ソースコードに直接パスワードを書いてしまっています。

git diff-tree e27e -p
e27ea6a1cf49021b9686c7e757fbf9adf60afb3f
diff --git a/sample.js b/sample.js
index 0a2fa57..9c892d8 100644
--- a/sample.js
+++ b/sample.js
@@ -2,7 +2,7 @@

 var db = /* なんか初期化処理 */ null;

-db.init();
+db.init({password: test}); /* パスワードを追加しちゃった! */
 // データベースに何か値を突っ込む
 db.save({id: 1, name: "ちさ"});
 db.close();

password: testの部分をpassword: xxxxに変えて、隠しましょう。

そのために、まず新しいブランチを用意します。

git checkout -b rewritten

として新たなブランチをつくった上で、

git rebase -i <修正したいコミットの一個前のコミット>

を実行して書き換え開始です。rebase -iでインタラクティブモードを使うと、コミットを直接書き換えることができるようです。するとインタラクティブなので、こういう感じでエディタが現れます。 →notepad++でgit logを編集したいとき

20130222-01

これで、テキストの上のほうに

pick e27ea6a パスワードを追加しちゃったコミット。取り消したい。
pick e236c0d ロード処理的なものを追加した(という想定)

とあるので、改変した「pick e27ea6a」を「edit e27ea6a」に書き換えてエディタを終了します。すると…

git rebase -i 7c132
Stopped at e27ea6a... パスワードを追加しちゃったコミット。取り消したい。
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

と指示が出てくるので、書き換えましょう。書き換えたソースがこれ。

// 以下のコードはあくまでサンプルであり、何の意味もありません。

var db = /* なんか初期化処理 */ null;

db.init({password: xxxx}); /* パスワードは隠しておいた */
// データベースに何か値を突っ込む
db.save({id: 1, name: "ちさ"});
db.close();

ちゃんと隠しました。

指示されたとおり–amendでコミットします。その前にgit addを忘れないように

git add sample.js
git commit --amend
[detached HEAD 6a7b87a] パスワードを追加したコミットだったけど、取り消しておいた
 1 file changed, 1 insertion(+), 1 deletion(-)

これで書き換えられましたとさ。で、残りのコミットをgit rebase –continueで再度適用しなおします。

$ git rebase --continue
Successfully rebased and updated refs/heads/rewritten.

gitk –allってやって全コミットを可視化すると…

20130222-02

ちゃんと枝分かれしてます。masterを消してrewritteをmasterにrenameすれば、完全に書き換えたことになります。

ここで注意しないと行けないのが、「ロード処理的なもの~」というコミットが2つ存在することです。rebase -iして書き換えると、その時点以降のコミットはすべて別のコミット扱いになるので、注意が必要です。

最初のコミットはどうするの?

上では修正したいコミットの一個前のコミットを指定しましたが、じゃあその一個前のコミットがない、最初のコミットはどうすればいいの?その場合のサンプルも用意してみました→github:ledyba/__sample__RewriteGitHist2

この時は若干面倒くさいです。

まず、テンポラリなブランチを作って…

git checkout -b tmp

reset –hardで一番最初まで戻ります。

git reset --hard <一番最初のコミット>

この状態で、消したい部分を修正して、コミットを修正しちゃってください!

vi <修正したいファイル>
git add <修正したいファイル>
git commit --amend

こうすると、tmpブランチはmasterブランチとは一切つながりのない孤立したブランチとなります。よく見てみてください。線が繋がってません。

20130222-03

ではここからどうするかというと、この新しい孤島のtmpに、書き換え以前のmasterのコミットを適用しなおします。

そのためにmasterの最初のコミットに、oldrootというタグをつけて、rebase –ontoを使います。

git rebase --onto tmp oldroot master

すると(うまくマージが動けば)修正済みのmasterブランチが出来上がってくるはずです。

20130222-04

昔の書き換えたいコミットである、oldrootが今度は孤島になりました。

過去コミットのAuthor/Comitterを書き換えたい

以前適当に使っていたので、設定する名前とメールアドレスも適当でした。マシンごとに違った設定だったり、セットアップしなおすたびに適当に違うメールアドレス入れたり。

$git config --global user.name fuga
$git config --global user.email hoge@gmail.com

でもそうすると、誰がコミットしたのか訳がわからなくなってしまって、困ってしまいます。ので、公開するときにはこれを統一したい。そう思いました。

その方法ですが、stack overflowに書いてあるのでそのまま受け売りすると、filter-branchを使うと書き換えられるようです。

 git filter-branch --commit-filter '
        if [ "$GIT_COMMITTER_NAME" = "<Old Name>" ];
        then
                GIT_COMMITTER_NAME="<New Name>";
                GIT_AUTHOR_NAME="<New Name>";
                GIT_COMMITTER_EMAIL="<New Email>";
                GIT_AUTHOR_EMAIL="<New Email>";
                git commit-tree "$@";
        else
                git commit-tree "$@";
        fi' HEAD

とやって、コミット時にいろいろ書き換えられます。このcommit filterのなかに書いてあるのはシェルスクリプトで、sedを使ってファイルを書き換えることもできますし、上記のように環境変数を指定することで、メタデータも書き換えられます。書き換えられるメタデータは以下:

  • GIT_AUTHOR_NAME
  • GIT_AUTHOR_EMAIL
  • GIT_AUTHOR_DATE
  • GIT_COMMITTER_NAME
  • GIT_COMMITTER_EMAIL
  • GIT_COMMITTER_DATE

特定のファイルを全部除去したい

今度は例えば、画像処理プログラムのサンプルで、著作権的によろしくない画像を使ってた…みたいなケースです。ありそう(過去を思い出しながら)

上記のfilter-branchではコマンドラインを使うと任意のシェル・コマンドが使えますので、

git filter-branch --commit-filter 'rm -f <消したいファイルとか>' HEAD

ってやれば、ファイルとか全部消せます。examplesにいろいろかいてあるので試してみてね

おわり

rebaseの話はあんまり見ないので、そちらを重点に、以前やったことがある書き換えについて書いてみました。gitのブランチを詰将棋みたいに書き換えるゲームもあったりするようで、gitのこの辺の機能は強力であると同時に少し難しいですね…。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください