読者です 読者をやめる 読者になる 読者になる

すたらブログ

文系Webプログラマの備忘録

Git: コミット履歴の順番を変えたい

要望

Gitで管理する前に作成していた古いソースコードを、Gitでの管理を始めたバージョンよりも前のコミット履歴に挿入したい。
あたかも、古いソースコードも初めからGitで管理していたかのように見せたい。


解決策

git rebase -iを使ってコミット履歴の順番を入れ替えます。


手順

1. 古いソースコードをコミットする

まず、古いソースコードをそのままコミットします。
履歴は下記のようになります。

$ git log --oneline
c2c2028 過去に挿入したいコミット
38ecb35 第5回目の変更
b88bc77 第4回目の変更
224be5e 第3回目の変更
5a15e86 第2回目の変更
a9c1f0a 第1回目の変更
2809352 test.txtを追加
2f3f471 最初のコミット

その上で、「過去に挿入したいコミット」を「test.txtを追加」の後に挿入します。


2. 「git rebase -i」を実行する

挿入する場所の直前のコミットのIDを指定してgit rebase -iを実行します。

$ git rebase -i 2809352

画面がviに切り替わります。
yy, dd, Pなどのコマンドを使って、行をコピペしたり削除したりします。

  • yy: 一行をコピー
  • dd: 一行を削除
  • P(大文字): コピーしたものを貼り付ける。
    行の場合はカーソルの上に挿入される。

(参考: viコマンド(vimコマンド)一覧(検索・置換))

変更前
pick a9c1f0a 第1回目の変更
pick 5a15e86 第2回目の変更
pick 224be5e 第3回目の変更
pick b88bc77 第4回目の変更
pick 38ecb35 第5回目の変更
pick c2c2028 過去に挿入したいコミット

(以下略)
変更後
pick c2c2028 過去に挿入したいコミット
pick a9c1f0a 第1回目の変更
pick 5a15e86 第2回目の変更
pick 224be5e 第3回目の変更
pick b88bc77 第4回目の変更
pick 38ecb35 第5回目の変更

(以下略)

3. 衝突を解決する

viを終了すると、下記のようにエラーを告げられます。
過去に挿入したいコミットが割り込んだことで、その直前のtest.txtを追加とのマージに衝突が起こりました。
これを手動で解決しなければなりません。

(rebase 実行後の画面)
$ git rebase -i 2809352
error: could not apply c2c2028... 過去に挿入したいコミット

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not apply c2c2028470e3c67fd00fe515e0376ed30026d174... 過去に挿入したいコミ
ット

$

エディタで衝突箇所を確認します。
エラーメッセージにerror: could not apply c2c2028... 過去に挿入したいコミットとあるように、今回は過去に挿入したいコミットの時点でのソースコードの状態を整えます。

解決前
<<<<<<< HEAD
=======
過去に挿入したい文章
>>>>>>> c2c2028... 過去に挿入したいコミット
解決後
過去に挿入したい文章

4. コミットメッセージを変更する

衝突を解決したら、下記のようにrebaseを続けます。

$ git add test.txt
$ git rebase --continue

今度は過去に挿入したいコミットのコミットメッセージを変更するためにviに切り替わります。
が、変更する必要はないので:qで終了します。

(コミットメッセージ変更の画面)
過去に挿入したいコミット

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto 2809352
# You are currently rebasing branch 'master' on '2809352'.
#
# Changes to be committed:
#       modified:   test.txt
#

5. 衝突を解決する (その2)

今度は、過去に挿入したいコミットとその直後の第1回目の変更とのマージに衝突が起こりました。
これを解決します。

(rebase --continue 実行後の画面)
$ git rebase --continue
[detached HEAD e3057de] 過去に挿入したいコミット
 1 file changed, 1 insertion(+)

error: could not apply a9c1f0a... 第1回目の変更

When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not apply a9c1f0aa41a5ea1b5a495ee6bb94edfad4f9d7ee... 第1回目の変更

今回は第1回目の変更の時点でのソースコードの状態を整えます。

解決前
<<<<<<< HEAD
過去に挿入したい文章
=======
1
>>>>>>> a9c1f0a... 第1回目の変更
解決後
1

6. コミットメッセージを変更する (その2)

衝突を解決したら、rebaseを続けます。

$ git add test.txt
$ git rebase --continue

今回もコミットメッセージを変更する必要はありません。
:qでエディタを終了します。

(コミットメッセージ変更の画面)
第1回目の変更

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# rebase in progress; onto 2809352
# You are currently rebasing branch 'master' on '2809352'.
#
# Changes to be committed:
#       modified:   test.txt
#

7. rebase 完了

この後も各コミット間で衝突がないか検査されますが、途中で止まらずにrebaseが完了するはずです。

$ git rebase --continue
[detached HEAD 4300ca7] 第1回目の変更
 1 file changed, 1 insertion(+), 1 deletion(-)

Successfully rebased and updated refs/heads/master.

確認すると、順番が入れ替わっています。
また、IDが新たに割り振られています。

$ git log --oneline
29b1ca1 第5回目の変更
bceed4b 第4回目の変更
fd04728 第3回目の変更
2ab05f1 第2回目の変更
4300ca7 第1回目の変更
e3057de 過去に挿入したいコミット
2809352 test.txtを追加
2f3f471 最初のコミット

注意点

  1. すでにリモートにプッシュしているコミットの履歴は変更するべきではないでしょう。
  2. いろいろ試したのですが、最初のコミットよりも前に挿入することはできないようです。
    2番目に古い位置にしか挿入できない、ということです。