Gitの派生元ブランチの変更方法


概要

Gitで作業内容ごとにブランチを作成して作業をしていると,派生元ブランチを間違えてしまうことがある。

例えば,ある機能Aの開発中に,別機能Bの開発も必要になり,Aブランチで作業している最中に,Bブランチを作成すると,BブランチはAブランチが派生元になる。

この状態で,Bブランチをmasterに取り込もうとすると,Aブランチの内容を誤って取り込んでしまう。

Bブランチの作成時に,masterから作成するようにすれば何も問題ないのだが,場合によってはそうもいかないこともある。

そこで,派生元ブランチの変更方法を調べたので整理する。

以下の情報を参考にした。

情報源

方法としては,以下の2通りとなる。

  1. git rebase --onto <newbase>で派生元の付替え (push前限定)
  2. ブランチを作り直して,git cherry-pickで必要なコミットを取り込み

話をわかりやすくするために,AブランチからBブランチを誤って作ってしまったので,masterブランチを派生元にしたい場合を以下の例では説明する。

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 reaseを実行してはいけない。つまり,一度でも付け替え対象ブランチでgit pushしたら,この方法は使えない。

自分一人のプロジェクトの場合にかぎり,git push -fで強制的に上書きできる。それ以外の場合は,この方法は諦める。

元々git rebaseがコミットの派生元を付け替えるコマンドだ。当初,--ontoオプションの有無の違いがいまいち理解できなかったので,重点的に説明する。

公式の「Git – リベース」がわかりやすかった。

--onto <newbase>を指定すると,(現在の) 派生元ブランチ (<upstream>) の現在ブランチの派生元コミットを基準に,それ以降の現在ブランチのコミットを,--ontoの引数に指定したブランチ (<newbase>) の先頭に付け替えることができる。

--onto <newbase>を指定しない場合,(現在の) 派生元ブランチ (<upstream>) の現在ブランチの派生元コミットを基準に,それ以降の現在のブランチのコミットを,(現在の) 派生元ブランチ (<upstream>) の先頭に付け替える。

派生元ブランチのコミットの有無が決定的な違いだ。--onto <newbase>を指定すると,現在の派生元ブランチのコミットは含まれず,現在のブランチで作業したコミットだけを付け替えることができる。

--onto <newbase>を省くと現在の派生元のブランチのコミットを含んでしまう

git rebase --onto master A B # Bのコミットだけを含んだ状態でmasterの先頭に付け替える
git rebase master B # Aのコミットも含んだ状態でmasterの先頭に付け替える

ブランチは作業内容や目的を明確に分けるために作るので,そのブランチに本来関係ないコミットが含まれてしまうのは好ましくない。従って,--onto <newbase>で現在のブランチのコミットだけを付け替える必要がある。

例えば,以下の図で,clientブランは元々C9を指していた。この状態で,C8とC9をmasterにマージしたいけど,clientの派生元のserverブランチのC3はmasterブランチにマージしたくない場合,--onto <newbase>を指定すると実現できる。

具体的には以下のコマンドで実現できる。

git rebase --onto master server client

このコマンドの意味を解説する。まず--onto masterで,付け替え先を指定している。続く,serverでupstream (派生元) を指定,最後に,移動させるブランチclientを指定している。

これにより,clientの上流であるserverを基準にしてmasterに付け替える。つまり,clientの上流であるserverの派生地点のC3を基準に,それ以降のclientブランチのコミットをmasterに付け替えるという意味になる。

--ontoを指定しない場合,挙動が変わる。git rebase master clientを実行すると,C2が基準になるため,C3をC6に付け替えようとしてしまう。そのため,元々serverで作業していたC3がmasterに入ってしまう。

新規ブランチ作成後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してしまったとしても,リモートブランチやローカルブランチも削除してしまえばいい。あまり気負わずに,どんどんブランチを作って削除するくらいの気持ちでやるのがよい。

今後,派生元ブランチを間違えた場合,ここに記した情報で迷わずに対応したい。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です