この記事では、Gitリベースの基本操作とその利点について詳しく解説します。git rebaseコマンドを使ってブランチを整理し、コミット履歴を直線的に保つ方法を紹介。競合が発生した際の解決手順や、インタラクティブリベース(git rebase -i)を利用してコミットをまとめるテクニックも説明します。効率的にブランチを管理し、整理されたコミット履歴を維持するための具体的な手法を学びましょう。
目次
パッチを取得するブランチを指定し適用して現在のブランチのパッチを適用 (git rebase <ブランチ名>)
以下のようなコミット履歴を想定してgit rebaseを行います。
1 2 3 4 5 6 7 8 9 10 |
~/TestProject (master) $ git log --all --oneline --graph --decorate * c359653 (origin/b1, b1) 8 commit * 13eba7f 7 commit * 51e74cb 6 commit | * dac7b7a (HEAD -> master, origin/master) 5 commit | * 103a22d 4 commit | * 0607029 3 commit |/ * 70408ab 2 commit * 42b81ea 1 commit |
masterブランチでgit rebaseコマンドを実行します。リベースを行うとき、いくつかのファイルが競合する例をします。
まず、git rebaseで競合したときに使う特有のコマンドを紹介します。
コマンド | 説明 |
---|---|
git rebase --continue | 続けて競合を解決する操作を行う |
git rebase --skip | あるコミットでの競合の解決をスキップ |
git rebase --abort | 競合中のリベース操作を中断してリベース前まで操作を元に戻す |
masterブランチからgit rebaseでb1ブランチを引数にして、リベースを行います。結果として出来上がるmasterブランチはb1ブランチを適用されてからmasterブランチのコミットが適用されたものになり、b1ブランチの先を作成したようなイメージになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
~/TestProject (master) $ git rebase b1 Auto-merging file2.txt CONFLICT (content): Merge conflict in file2.txt Auto-merging file3.txt error: 0607029を適用できませんでした... 3 commit hint: Resolve all conflicts manually, mark them as resolved with hint: "git add/rm <conflicted_files>", then run "git rebase --continue". hint: You can instead skip this commit: run "git rebase --skip". hint: To abort and get back to the state before "git rebase", run "git rebase --abort". Could not apply 0607029... 3 commit ~/TestProject (master|REBASE 1/3) $ git mergetool -t vimdiff Merging: file2.txt Normal merge conflict for 'file2.txt': {local}: modified file {remote}: modified file 4 個のファイルが編集を控えています ~/TestProject (master|REBASE 1/3) $ git rebase --continue [detached HEAD 028ba4d] 3 commit 1 file changed, 1 insertion(+) Auto-merging file4.txt CONFLICT (add/add): Merge conflict in file4.txt error: 103a22dを適用できませんでした... 4 commit hint: Resolve all conflicts manually, mark them as resolved with hint: "git add/rm <conflicted_files>", then run "git rebase --continue". hint: You can instead skip this commit: run "git rebase --skip". hint: To abort and get back to the state before "git rebase", run "git rebase --abort". Could not apply 103a22d... 4 commit ~/TestProject (master|REBASE 2/3) $ git mergetool -t vimdiff Merging: file4.txt Normal merge conflict for 'file4.txt': {local}: created file {remote}: created file 3 個のファイルが編集を控えています ~/TestProject (master|REBASE 2/3) $ git rebase --continue [detached HEAD 9b92826] 4 commit 1 file changed, 1 insertion(+) Successfully rebased and updated refs/heads/master. ~/TestProject (master) $ |
上の例では一つのコマンド毎に出力が多く何のコマンドを行っているか分かりにくいと思うので、上の例のコマンド操作をまとめると以下のようなコマンドを使用しています。基本的にmerge操作の競合処理と似たような感じで処理していきます。
1 2 3 4 5 6 |
~/TestProject (master) $ git rebase b1 ~/TestProject (master|REBASE 1/3) $ git mergetool -t vimdiff ~/TestProject (master|REBASE 1/3) $ git rebase --continue ~/TestProject (master|REBASE 2/3) $ git mergetool -t vimdiff ~/TestProject (master|REBASE 2/3) $ git rebase --continue ~/TestProject (master) $ |
最終的なコミット履歴は以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
~/TestProject (master) $ git log --all --oneline --graph --decorate * f0be6f2 (HEAD -> master) 5 commit * 9b92826 4 commit * 028ba4d 3 commit * c359653 (origin/b1, b1) 8 commit * 13eba7f 7 commit * 51e74cb 6 commit | * dac7b7a (origin/master) 5 commit | * 103a22d 4 commit | * 0607029 3 commit |/ * 70408ab 2 commit * 42b81ea 1 commit |
一応補足として、リモートブランチを除いたブランチを確認すると一直線のコミット履歴を見ることができます。
1 2 3 4 5 6 7 8 9 |
~/TestProject (master) $ git log --branches --oneline --graph --decorate * f0be6f2 (HEAD -> master) 5 commit * 9b92826 4 commit * 028ba4d 3 commit * c359653 (origin/b1, b1) 8 commit * 13eba7f 7 commit * 51e74cb 6 commit * 70408ab 2 commit * 42b81ea 1 commit |
リベース操作の説明としてこれで終わりになりますが、これをリモートブランチにプッシュしようとして簡単な操作を行うとb1ブランチにつなげる形でコミット履歴がプッシュできるようにできます。これを意図したものかどうかはわかりませんが、masterブランチを主流の流れだとするとおそらく違うものになると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
~/TestProject (b1) $ git log --all --oneline --graph --decorate * f0be6f2 (HEAD -> b1, origin/b1, master) 5 commit * 9b92826 4 commit * 028ba4d 3 commit * c359653 8 commit * 13eba7f 7 commit * 51e74cb 6 commit | * dac7b7a (origin/master) 5 commit | * 103a22d 4 commit | * 0607029 3 commit |/ * 70408ab 2 commit * 42b81ea 1 commit |
masterブランチにつながるようにリベース
以下のコミット履歴に戻ってやり直します。これは裏であらかじめリポジトリをコピーしたものを使用しています。
1 2 3 4 5 6 7 8 9 10 |
~/TestProject (master) $ git log --all --oneline --graph --decorate * c359653 (origin/b1, b1) 8 commit * 13eba7f 7 commit * 51e74cb 6 commit | * dac7b7a (HEAD -> master, origin/master, origin/HEAD) 5 commit | * 103a22d 4 commit | * 0607029 3 commit |/ * 70408ab 2 commit * 42b81ea 1 commit |
masterブランチにつなげるようにするには、一度b1ブランチに移動してからリベース操作を行う必要があります。出力自体は上の例と似たようなものになるので操作部分のみを記述します。
1 2 3 4 5 6 7 |
~/TestProject (master) $ git checkout b1 ~/TestProject (b1) $ git rebase master ~/TestProject (b1|REBASE 1/3) $ git mergetool -t vimdiff ~/TestProject (b1|REBASE 1/3) $ git rebase --continue ~/TestProject (b1|REBASE 2/3) $ git mergetool -t vimdiff ~/TestProject (b1|REBASE 2/3) $ git rebase --continue ~/TestProject (b1) $ |
このようにするとコミット履歴は
1 2 3 4 5 6 7 8 9 |
~/TestProject (b1) $ git log --branches --oneline --graph --decorate * 80add57 (HEAD -> b1) 8 commit * 2651e26 7 commit * 85f88aa 6 commit * dac7b7a (origin/master, origin/HEAD, master) 5 commit * 103a22d 4 commit * 0607029 3 commit * 70408ab 2 commit * 42b81ea 1 commit |
のようになります。次にmasterブランチに戻り、git mergeを使い、fast-forwardでmasterブランチを進めます。
1 2 3 4 5 6 7 8 9 10 11 12 |
~/TestProject (b1) $ git checkout master Switched to branch 'master' Your branch is up to date with 'origin/master'. ~/TestProject (master) $ git merge b1 Updating dac7b7a..80add57 Fast-forward file2.txt | 1 + file3.txt | 1 + file4.txt | 1 + file6.txt | 0 4 files changed, 3 insertions(+) create mode 100644 file6.txt |
そうするとmasterブランチのコミット履歴は
1 2 3 4 5 6 7 8 9 |
~/TestProject (master) $ git log --branches --oneline --graph --decorate * 80add57 (HEAD -> master, b1) 8 commit * 2651e26 7 commit * 85f88aa 6 commit * dac7b7a (origin/master, origin/HEAD) 5 commit * 103a22d 4 commit * 0607029 3 commit * 70408ab 2 commit * 42b81ea 1 commit |
のようになります。この状態でpushを行うとmasterブランチの方にコミットを追加できるようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
~/TestProject (master) $ git push (出力省略) ~/TestProject (master) $ git log --all --oneline --graph --decorate * 80add57 (HEAD -> master, origin/master, origin/HEAD, b1) 8 commit * 2651e26 7 commit * 85f88aa 6 commit * dac7b7a 5 commit * 103a22d 4 commit * 0607029 3 commit | * c359653 (origin/b1) 8 commit | * 13eba7f 7 commit | * 51e74cb 6 commit |/ * 70408ab 2 commit * 42b81ea 1 commit |
リモートのb1ブランチを削除したい場合は
1 |
git push -d origin b1 |
で削除できます(リモートリポジトリの名前としてoriginを利用しています)。その後にローカルのブランチは
1 |
git branch -d b1 |
で削除できます。
最終的に残るコミット履歴は以下のようになります。
1 2 3 4 5 6 7 8 9 |
~/TestProject (master) $ git log --all --oneline --graph --decorate * 80add57 (HEAD -> master, origin/master, origin/HEAD) 8 commit * 2651e26 7 commit * 85f88aa 6 commit * dac7b7a 5 commit * 103a22d 4 commit * 0607029 3 commit * 70408ab 2 commit * 42b81ea 1 commit |
ただし、コミット履歴からb1ブランチの流れが消えてしまうのでリモートブランチで複数のブランチを管理している場合、ブランチを統合するような目的だとrebaseではなくmergeで操作を行ったほうが良いかもしれません。または、複数人で共有するようなリモートブランチでメモのようなブランチを管理せずに重要なブランチのみを管理しても良いかもしれません。
リベース操作を行うのに都合がいいところとしてgit pullを行うときにリベースでリモートリポジトリの内容を取り込む方法があります。これはリモートでの作業の例で説明しています。
適用するパッチを対話的に選択(git rebase -i <ブランチ名>)
以下のようなコミット履歴に対して、git rebase -iの例を示します。この例の意図はmasterブランチの後にb1ブランチのコミット内容を繋げる操作になります。
1 2 3 4 5 6 7 8 9 10 |
~/TestProject (b1) $ git log --all --oneline --graph --decorate * c359653 (HEAD -> b1, origin/b1) 8 commit * 13eba7f 7 commit * 51e74cb 6 commit | * dac7b7a (origin/master, origin/HEAD, master) 5 commit | * 103a22d 4 commit | * 0607029 3 commit |/ * 70408ab 2 commit * 42b81ea 1 commit |
git rebase -iで適用するパッチを選択するためのエディタが開きます。
1 |
~/TestProject (b1) $ git rebase -i master |
エディタが開いたら、以下のような内容を編集してgit rebase -iの動作を変更できます。
編集する部分は'pick'と書かれている部分で、これはコマンドになります。対応するコミットIDやコミットメッセージがあるので、行いたいコマンドに変えていきます。
また、コマンドの種類はコメントの部分で書かれています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
pick 51e74cb 6 commit pick 13eba7f 7 commit pick c359653 8 commit # Rebase dac7b7a..c359653 onto dac7b7a (3 commands) # # Commands: # p, pick <commit> = use commit # r, reword <commit> = use commit, but edit the commit message # e, edit <commit> = use commit, but stop for amending # s, squash <commit> = use commit, but meld into previous commit # f, fixup [-C | -c] <commit> = like "squash" but keep only the previous # commit's log message, unless -C is used, in which case # keep only this commit's message; -c is same as -C but # opens the editor # x, exec <command> = run command (the rest of the line) using shell # b, break = stop here (continue rebase later with 'git rebase --continue') # d, drop <commit> = remove commit (コメントはまだ続きますが省略) |
この例では7と8番目のコミットを6番目のコミットに統合したいと思います。7と8番目のコミットをs(squash)に変更し保存して終了します。
1 2 3 |
pick 51e74cb 6 commit s 13eba7f 7 commit s c359653 8 commit |
その後は、通常のリベース操作のように競合があれば解決していきます。以下は今回の例で利用しているコマンドの流れになります。
1 2 3 4 5 6 |
~/TestProject (b1) $ git rebase -i master ~/TestProject (b1|REBASE 1/3) $ git mergetool -t vimdiff ~/TestProject (b1|REBASE 1/3) $ git rebase --continue ~/TestProject (b1|REBASE 2/3) $ git mergetool -t vimdiff ~/TestProject (b1|REBASE 2/3) $ git rebase --continue (git rebase -iの完了) |
それらのコマンドを利用していった流れの全体は以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
~/TestProject (b1) $ git rebase -i master Auto-merging file2.txt CONFLICT (content): Merge conflict in file2.txt Auto-merging file3.txt error: 51e74cbを適用できませんでした... 6 commit hint: Resolve all conflicts manually, mark them as resolved with hint: "git add/rm <conflicted_files>", then run "git rebase --continue". hint: You can instead skip this commit: run "git rebase --skip". hint: To abort and get back to the state before "git rebase", run "git rebase --abort". Could not apply 51e74cb... 6 commit ~/TestProject (b1|REBASE 1/3) $ git mergetool -t vimdiff Merging: file2.txt Normal merge conflict for 'file2.txt': {local}: modified file {remote}: modified file 4 個のファイルが編集を控えています ~/TestProject (b1|REBASE 1/3) $ git rebase --continue [detached HEAD 0fbfaf9] 6 - 8 commit 2 files changed, 2 insertions(+) Auto-merging file4.txt CONFLICT (add/add): Merge conflict in file4.txt error: 13eba7fを適用できませんでした... 7 commit hint: Resolve all conflicts manually, mark them as resolved with hint: "git add/rm <conflicted_files>", then run "git rebase --continue". hint: You can instead skip this commit: run "git rebase --skip". hint: To abort and get back to the state before "git rebase", run "git rebase --abort". Could not apply 13eba7f... 7 commit ~/TestProject (b1|REBASE 2/3) $ git mergetool -t vimdiff Merging: file4.txt Normal merge conflict for 'file4.txt': {local}: created file {remote}: created file 3 個のファイルが編集を控えています ~/TestProject (b1|REBASE 2/3) $ git rebase --continue [detached HEAD e80ec73] 6 - 8 commit Date: Sun Apr 28 06:50:02 2024 +0900 3 files changed, 3 insertions(+) [detached HEAD c553523] 6 - 8 commit Date: Sun Apr 28 06:50:02 2024 +0900 4 files changed, 3 insertions(+) create mode 100644 file6.txt Successfully rebased and updated refs/heads/b1. |
リベース操作が完了した後のリモートブランチを含んだ全体のログは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 |
~/TestProject (b1) $ git log --all --oneline --graph --decorate * c553523 (HEAD -> b1) 6 - 8 commit * dac7b7a (origin/master, origin/HEAD, master) 5 commit * 103a22d 4 commit * 0607029 3 commit | * c359653 (origin/b1) 8 commit | * 13eba7f 7 commit | * 51e74cb 6 commit |/ * 70408ab 2 commit * 42b81ea 1 commit |
また、リベースしたブランチのみに注目したコミット履歴は以下のようになります。
1 2 3 4 5 6 7 |
~/TestProject (b1) $ git log --oneline --graph --decorate * c553523 (HEAD -> b1) 6 - 8 commit * dac7b7a (origin/master, origin/HEAD, master) 5 commit * 103a22d 4 commit * 0607029 3 commit * 70408ab 2 commit * 42b81ea 1 commit |
この後の操作は、ローカルのmasterブランチを進めて、リモートリポジトリのmasterブランチにプッシュすることになるでしょう。
コミットをまとめる
git rebase -iはコミットをまとめるのに利用できます。例えば、リモートリポジトリにまだプッシュを行っていないコミットで、機能的に同じ部分のコミットであり軽微な修正のコミットを出してしまったときにコミットをまとめたい場合があります。そのようなときに使用するのに便利です。
以下のようなコミット履歴を例にします。
1 2 3 4 5 |
~/Proj (master) $ git log --oneline --graph --decorate * 9303fd4 (HEAD -> master) 3-2 commit * 77ebfd8 3 commit * 3b5fb54 (origin/master) 2 commit * 426f909 1 commit |
git rebase -iで現在のブランチの最新のコミットから2つ前(この例の3b5fb54)を指定することで、その指定したコミットを含まずに最新のコミットまでの間のコミットでエディタが開きます。
1 |
git rebase -i HEAD~2 |
または、コミットを直接指定して、
1 |
git rebase -i 3b5fb54 |
または、まとめたいコミットまでの範囲で一番古いコミットを直接指定して、そのコミットに直前を表す(^)をつけて指定する方法もあります。
1 |
git rebase -i 77ebfd8^ |
これらはすべて、エディタで以下のような内容のものが開かれます。
1 2 3 4 5 6 7 |
pick 77ebfd8 3 commit pick 9303fd4 3-2 commit # Rebase 3b5fb54..9303fd4 onto 3b5fb54 (2 commands) # # Commands: (コメント省略) |
git rebase -iで一番最初のコミットを選択したい場合は--rootオプションが利用できます。
1 |
git rebase -i --root |
また、この例で一番最初のコミットや最初から2番目のコミットを修正した場合、その修正をリモートリポジトリに反映させるためには強制プッシュを行わなければいけないところに注意が必要でしょう。
git rebase -iでまとめた結果として
1 2 3 4 |
~/Proj (master) $ git log --oneline --graph --decorate * 538fe21 (HEAD -> master) 3 commit * 3b5fb54 (origin/master) 2 commit * 426f909 1 commit |
としたとき、これはリモートリポジトリのコミットをいじっていないので通常のプッシュでリモートリポジトリに記録できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
~/Proj (master) $ git push Enumerating objects: 4, done. Counting objects: 100% (4/4), done. Delta compression using up to 2 threads Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 270 bytes | 270.00 KiB/s, done. Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 To /home/ubuntu/git/rebase-intaractive.git 3b5fb54..538fe21 master -> master ~/Proj (master) $ git log --oneline --graph --decorate * 538fe21 (HEAD -> master, origin/master) 3 commit * 3b5fb54 2 commit * 426f909 1 commit |
これはローカルリポジトリにあるコミット履歴を整理したい場合に利用できるテクニックになります。