Gitでの派生元ブランチの変更方法
概要
Gitで作業内容ごとにブランチを作成して作業をしていると,派生元ブランチを間違えてしまうことがある。
例えば,ある機能Aの開発中に,別機能Bの開発も必要になり,Aブランチで作業している最中に,Bブランチを作成すると,BブランチはAブランチが派生元になる。
この状態で,Bブランチをmasterに取り込もうとすると,Aブランチの内容を誤って取り込んでしまう。
Bブランチの作成時に,masterから作成するようにすれば何も問題ないのだが,場合によってはそうもいかないこともある。
そこで,派生元ブランチの変更方法を調べたので整理する。
以下の情報を参考にした。
方法は以下の2通りとなる。
git rebase --onto <newbase>
で派生元の付替- ブランチを作り直して,
git cherry-pick
で必要なコミットを取り込み
話をわかりやすくするために,以下の状況の例で説明する。
- masterが派生元のAブランチからBブランチを誤って作ってしまった。
- Aブランチでなく、masterブランチをBの派生元にしたい。
git rebase --onto <newbase>
1個目の方法はgit rebase --onto <newbase>
コマンドを使う。
git rebase --onto <newbase> <upstream> [<branch>]
git rebase --onto 新しい派生元 現在の派生元 [付け替え対象ブランチ]
最後のブランチを省略すると,現在のブランチを指定したとみなされる。
git rebase --onto master A B
大前提として,git rebase
は過去のコミットを改変する。そのため,付け替え対象ブランチでgit push
した場合,確実にコンフリクトする。
コンフリクトを回避したければ、「Gitで一度pushしたブランチでgit commit –amend/git rebase後再度pushする方法 – senooken.jp」に記載した通り、一度リモートブランチを削除してからgit push
する必要がある。リモートブランチを削除できない場合、ブランチ名を変更するなどする必要がある。そのほか、自分一人のプロジェクトの場合にかぎり,git push -f
で強制的に上書きして対応する。
元々、git rebase
は2個のブランチを比較して、片方のブランチのコミットをもう片方のブランチに適用 (rebase) するコマンドだ。
--onto
オプションを使うことで、コミットの適用先 (ブランチの付け替え先) を別のブランチに指定できる。当初,--onto
オプションの有無の違いがいまいち理解できなかったので,重点的に説明する。
公式の「Git – リベース」がわかりやすかった。
--onto <newbase>
を指定すると,upstream (A) と付け替え対象ブランチ (B) の2ブランチ間の差分 (Bブランチのコミットだけ) を--onto
の引数に指定したブランチ (<newbase> (master)) の先頭に付け替えることができる。
--onto <newbase>
を指定しない場合 (git rebase <newbase>
)、<newbase> (master) と付け替え対象ブランチ (B) の2ブランチ間の差分 (A+Bのコミット) を<newbase>の先頭に付け替える。
付け替えの基準コミットが異なる。派生元ブランチのコミットの有無が決定的な違いだ。--onto <newbase>
を指定すると,現在の派生元ブランチのコミットは含まれず,現在のブランチで作業したコミットだけを付け替えることができる。
--onto <newbase>
を省くと現在の派生元のブランチのコミットを含んでしまう。
git rebase --onto master A B # Bのコミットだけを含んだ状態でmasterの先頭に付け替える
git rebase master B # Aのコミットも含んだ状態でmasterの先頭に付け替える
ブランチは作業内容や目的を明確に分けるために作るので,そのブランチに本来関係ないコミットが含まれてしまうのは好ましくない。従って,--onto <newbase>
で現在のブランチのコミットだけを付け替える必要がある。
例えば,以下のワーキングツリーでtopicの派生元をmasterに差し替える場合を考える。
o---o---o---o---o master \ o---o---o---o---o next \ o---o---o topic
この場合、以下のコマンドで狙い通りの派生元ブランチの付け替えを実現できる。
git rebase --onto master next topic
o---o---o---o---o master | \ | o'--o'--o' topic \ o---o---o---o---o next
逆に--onto
を使わないと、以下のようにnextのコミットも含んでしまう。
git rebase master topic
o---o---o---o---o master \ o---o---o---o---o next \ o---o---o topic
新規ブランチ作成後git cherry-pick
誤って,現在ブランチでgit push
した場合や,git rebase
でコンフリクトして,マージが面倒になった場合,こちらの方法で派生元ブランチを変更する。
この方法は,一旦現在のブランチを諦めて,派生元をちゃんと意識して新規にブランチを作成して,元々のブランチの必要なコミットだけを取り込む。
具体的には以下のコマンドを実行する。
git checkout <意図した派生元ブランチ>
git checkout -b <新規ブランチ>
git cherry-pick <元の作業ブランチの必要なコミット>
git checkout master
git checkout -b B2
git cherry-pick (Bのコミットをgit log Bで確認して必要なコミットのSHA1を指定)
元々作業していたブランチ (B) で必要なコミットを新しいブランチ (B2) に取り込み終わったら,最後に,古いブランチを「Gitのリモートブランチとローカルブランチの削除方法 – senooken.jp」に記した方法で削除すれば完了だ。
今回の場合,以下のコマンドを実行すればいい。
git push -d origin B
git branch -D B
これで見かけ上,派生元ブランチの付け替えが完了となる。
git cherry-pick
で1個ずつ取り込むのが少々面倒だが,git rebase
と異なり,git push
した場合でも適用できる。その他,git rebase
のように,コンフリクトの危険性もどちらかというと少ない。
結論
gitの派生元ブランチの変更方法を整理した。
基本的に,git push
していなければ,git rebase --onto <newbase>
を使うのがいい。
それ以外の,git push
した場合やgit rebase
のコンフリクトが面倒くさい場合,ブランチを作り直して必要なコミットだけ元ブランチから取り込んだ方が手っ取り早い。
万が一,git push
してしまったとしても,リモートブランチやローカルブランチも削除してしまえばいい。あまり気負わずに,どんどんブランチを作って削除するくらいの気持ちでやるのがよい。
今後,派生元ブランチを間違えた場合,ここに記した情報で迷わずに対応したい。