gitで最後のn個のコミットを残して2個目からその手前までをまとめるbashスクリプトを書いた

# なぜ作ったか

ソースコードのバージョン管理などgit通常の使い方であれば全ての履歴をとっておきたいと思います。が、単に何世代かのバックアップをとっておきたいだけなら、古いコミットはまとめてしまって参照されなくなったファイルは.gitから消してディスク容量を抑えたいところです。ということでスクリプトを書いてみました。

# ソース

https://github.com/hnakamur/git_squash_but_last_n_commits
に置きました。OS X 10.8.5 (Homebrewのgit 2.1.3とOS X標準のsed)とCentOS 6.6 (git 1.7.1とsed 4.2.1)で動作確認しました。

# 例

## 変更前

```
% git log
commit d08f035be73e38cfe20fdf492302ca0dab659f90
Author: Hiroaki Nakamura <hnakamur@gmail.com>
Date:   Sat Nov 15 21:10:49 2014

    Add hello6

commit 8424ea78bf5a9a5634b6a647b2088ff255ce452f
Author: Hiroaki Nakamura <hnakamur@gmail.com>
Date:   Sat Nov 15 21:10:48 2014

    Add hello5

commit aaa014a51dc1c4bcdc6ba82327eb12d230384c6a
Author: Hiroaki Nakamura <hnakamur@gmail.com>
Date:   Sat Nov 15 21:10:43 2014

    Add hello4

commit 2027c22669fb02734c4ae5e20a02d3823a393878
Author: Hiroaki Nakamura <hnakamur@gmail.com>
Date:   Sat Nov 15 21:10:43 2014

    Add hello3

commit cc4acfd45dfdc96cfc77cb063f7dde27f954bb6e
Author: Hiroaki Nakamura <hnakamur@gmail.com>
Date:   Sat Nov 15 21:10:42 2014

    Add hello2

commit 648a7d26a6bdb18cc795824499e8abd4e7e8e28b
Author: Hiroaki Nakamura <hnakamur@gmail.com>
Date:   Sat Nov 15 21:10:30 2014

    Initial commit
```

## コミットをまとめる変更操作

最後の何個のコミットを残すかを引数に指定して `git_squash_but_last_n_commits.sh` を呼び出します。

```
% git_squash_but_last_n_commits.sh 2
```

## 変更後

```
% git log
commit 752513b0c510bd69d143d91778936b74721ce6a2
Author: Hiroaki Nakamura <hnakamur@gmail.com>
Date:   Sat Nov 15 21:10:49 2014

    Add hello6

commit 3b0f962b9b10ecbab97db37e34c89b3e9ea0b892
Author: Hiroaki Nakamura <hnakamur@gmail.com>
Date:   Sat Nov 15 21:10:48 2014

    Add hello5

commit 504c12abe51fdc6b1fa38c7bce985c2449661957
Author: Hiroaki Nakamura <hnakamur@gmail.com>
Date:   Sat Nov 15 21:10:42 2014

    Add hello2

    Add hello3

    Add hello4

commit 648a7d26a6bdb18cc795824499e8abd4e7e8e28b
Author: Hiroaki Nakamura <hnakamur@gmail.com>
Date:   Sat Nov 15 21:10:30 2014

    Initial commit
```

# 実装メモ

## git rebase -i を非インタラクティブに使う

[stackoverflowに上がっていた回答]( http://stackoverflow.com/questions/7275508/is-there-a-way-to-squash-a-number-of-commits-non-interactively/18914084#18914084 )を参考にしました。

`git rebase -i` で編集する画面をsedで編集して `pick` を適宜 `squash` に変えてコミットをまとめています。

## エディタの指定方法

元の例では環境変数 `EDITOR` に設定していますが、 `git config` で `core.editor` を設定していると効きませんでした。そこで、元の `core.editor` の値をとっておいて、 `core.editor` の値を変更して `git rebase -i` を実行し、その後 `core.editor` の設定を元に戻すことで対応しています。

2014-11-16 00:25頃追記

@kyanro@github さんからのコメントで知ったのですが、git 1.7.2以降ならgit configで一旦変えて後に戻すという面倒なことをしなくても `-c` オプションで1回のコマンド実行だけ値を指定することもできるそうです。しかし、CentOS 6.xでyumで入れたgitは1.7.1なので `-c` は使えませんでした。そこで `-c` オプションが使えるかどうかで分岐するようにしました。

## 最初のコミットはまとめないで残すことにした

[最初OS Xで試した時のスクリプト]( https://github.com/hnakamur/git_squash_but_last_n_commits/blob/105791973cd5e969563304a4518e7a1dbfc89a2e/git_squash_but_last_n_commits.sh#L23 )では `git rebase -i --root` を使って最初のコミットも含めてまとめるようにしていました。

が、CentOS 6.6上のgit 1.7.1では `You must specify --onto when using --root` と言ってエラーになってしまいました。ふと考えると、最初のコミットは残しておいたほうがgitレポジトリを作成した日時を後から確認できるので良いなと思い直して、残すのが仕様ということにしました。

## 履歴から参照されなくなったファイルをgit gcで削除

[BFG Repo-Cleaner by rtyley]( http://rtyley.github.io/bfg-repo-cleaner/ )に書いてあった手順を真似しました。

```
$ git reflog expire --expire=now --all
$ git gc --prune=now --aggressive
```