Table of Contents
よくある3つの困ったシナリオ
Gitを使っていると、意図せずコミット履歴が複雑になってしまうことがあります。これから紹介するのは、多くの開発者が一度は経験するであろう代表的なシナリオです。
シナリオ1: 他のフィーチャーブランチから派生してしまった
本来は最新の main ブランチから切るべき自分の作業ブランチ (my-feature) を、うっかり同僚が開発中の dev-feature から派生させてしまったケース。このままでは、Pull Requestに無関係なコミットが含まれてしまいます。
問題の状況
|
1 2 3 4 5 6 |
* commit C (HEAD -> my-feature) * commit B * commit A (dev-feature) ←本当はここから派生したくなかった | * 最新のmain (main) |/ * ... |
シナリオ2: 一つのブランチに複数の変更が混在してしまった
一つのブランチ (work-branch) で作業中、コミットCとDが実は別の機能 (new-idea) であることに気づいたケース。この2つのコミットだけを、新しいブランチとしてきれいに分離したい状況です。
問題の状況
|
1 2 3 4 5 6 7 |
* commit E (HEAD -> work-branch) * commit D ┓ * commit C ┛ ←この2つだけを分離したい * commit B * commit A * 最新のmain (main) * ... |
シナリオ3: 古いブランチから新しい作業を始めてしまった
数ヶ月前に作ったまま放置していた old-feature ブランチ上で、新しい作業 (new-work) を始めてしまったケース。main ブランチはその間に大きく進んでおり、古い履歴の上に新しいコミットが乗ってしまっています。
問題の状況
|
1 2 3 4 5 6 7 8 |
* commit Y (HEAD -> new-work) * commit X | * かなり進んだ最新のmain (main) | | * ... | |/ | * commit B (old-feature) ←古い派生元 |/ * 昔のmain |
これらの悩みはすべて git rebase --onto で解決できます。
解決の鍵: git rebase --onto とは?
git rebase --onto は、「どのコミットから(from)」「どのコミットまで(to)を」「どこへ(onto)」移動させるかを正確に指定できる、非常に強力なリベースコマンドです。
コマンドの基本構文
|
1 |
git rebase --onto [新しい派生元] [元の派生元] [移動させたいブランチの先端] |
この3つの引数を理解することが、このコマンドを使いこなす鍵となります。
実践:シナリオ別コマンド解説
シナリオ1: 他のフィーチャーブランチから派生
それでは、最も一般的な「シナリオ1: 他のフィーチャーブランチから派生してしまった」ケースを例に、具体的な使い方を見ていきましょう。
やりたいこと: my-feature ブランチを、dev-feature からではなく main から派生した形に付け替える。
コマンド解説
|
1 2 3 4 5 6 |
# --onto main <- 新しい派生元は「main」 # dev-feature <- 元の派生元は「dev-feature」。このコミットは含まず、これ以降のコミットが対象。 # my-feature <- 移動させたいコミットが含まれるブランチは「my-feature」。 git rebase --onto main dev-feature my-feature |
このコマンドひとつで、Gitは以下のように履歴を再構築してくれます。
リベース後の理想的な状況
|
1 2 3 4 5 6 7 |
* commit C' (HEAD -> my-feature) * commit B' | * 最新のmain (main) | | * commit A (dev-feature) | |/ |/ * ... |
my-feature ブランチのコミット(B’, C’)が main の上にきれいに移動し、dev-feature とは無関係になりました。
シナリオ2: ブランチから一部のコミットを分離する
やりたいこと: work-branch の中のコミットCとDを、new-idea ブランチとして main から派生させる。この場合は、コミットIDを直接使います。
|
1 2 3 4 5 6 7 8 9 10 |
# 分離したい範囲の最初のコミットCの「親」のIDを取得 git log --oneline work-branch # commit C の親 (commit B) のIDをコピー # 1. 新しいブランチを作成 git switch -c new-idea work-branch # 2. rebase --onto でコミットを移動 # --onto [新しい派生元] [移動元の親] [移動させたい範囲の最後] git rebase --onto main <commit_B_ID> <commit_D_ID> |
これにより、new-idea ブランチにはコミットCとDだけが main の上に再配置されます。
シナリオ3: 古い派生元から最新のベースへ移動
やりたいこと: new-work ブランチを、古い old-feature の上から、最新の main の上へ移動させる。
|
1 2 3 |
# --onto [新しい派生元] [元の派生元] [移動させたいブランチ] git rebase --onto main old-feature new-work |
コマンドの形はシナリオ1と全く同じです。rebase --onto は、ブランチ間の距離がどれだけ離れていても問題なく動作します。
安全に実行するための注意点とTips
リモートリポジトリへのプッシュ
リベースはローカルのコミット履歴を書き換えます。リベース後にリモートへプッシュする際は、チームメンバーに影響を与える git push --force は避け、より安全な --force-with-lease を使いましょう。
|
1 2 3 |
# リモートの変更を意図せず上書きするのを防ぎつつ、強制プッシュ git push --force-with-lease origin [branch-name] |
リベース中にコンフリクトが発生した場合
リベースはコミットを一つずつ再適用していくため、途中でコンフフリクトが発生することがあります。その際は慌てず、以下の手順で対処しましょう。
- コンフリクトしたファイルを修正する: エディタでファイルを開き、
<<<<<<<,=======,>>>>>>>のマーカーを参考に、コードを正しい状態に修正します。 - 修正したファイルをステージングする:
1git add [修正したファイル名] - リベースを続行する:
1git rebase --continue
もし、コンフリクト解決が困難でリベース作業を中断したくなった場合は、以下のコマンドでリベース開始前の状態に戻せます。
|
1 |
git rebase --abort |
総括
git rebase --onto は、一見すると複雑に感じるかもしれませんが、「どこからどこまでを、どこへ」という概念を理解すれば、Gitの歴史を自由自在に整理できる魔法の杖となります。
今回ご紹介したシナリオに心当たりがあれば、ぜひこのコマンドを試してみてください。ただし、共有済みのブランチで履歴を書き換える際は、チームとコミュニケーションを取ることを忘れないようにしましょう。
