Gitでブランチの派生元を間違えたときの解決方法(rebase –onto、cherry-pick)

Gitを使って複数人で開発を行う場合、自分の作業用に「ブランチ」を作る機会が多いと思います。
Git初心者の人であっても、はじめの方に触れる工程のひとつですよね。グランフェアズではブランチ作成のコマンドを次のように教えています。

$ git checkout -b {作成するブランチ名} {親にするブランチ名}

ところが、この{親にするブランチ名}が正しく指定されない(=ブランチの派生元を間違える)と後々問題になってしまうことも…。実際にどんなことがあったのか、事例をもとに見ていきましょう。

「ブランチの派生元を間違える」とは

派生元の間違い方として思いつくのはこんなところでしょうか。

  • 親にするブランチが古かった(ブランチ作成前にgit fetch originを忘れた)
  • ブランチ作成時、予定と違うブランチを親にしてしまった。作業は進めてしまったが、まだコミットはしていない。
  • ブランチ作成時、予定と違うブランチを親にしてしまった。なおかつコミットもしてしまった。

当記事でメインに取り上げたいのはこの3つ目のケースです。続けてもう少し詳しく経緯を書いてみます。

具体的にこんなことがありました

同時進行する「作業A」「作業B」があり、どちらも「masterブランチから作業ブランチを作るように」と指示されたものとしましょう。

  # 作業Aのブランチ作成
  $ git fetch origin
  $ git checkout -b work/A origin/master
  # 作業A実施。適宜コミットなどもしてひと段落
  # 作業Bのブランチを作成
  $ git fetch origin
  $ git checkout -b work/B

work/Bブランチを作ったときのコマンドに注目です。「どのブランチを親にするか」指定を忘れてしまっています
こうすると、実は直前までいたwork/Aを親にブランチが作られた形になるのです。

気付かず作業・コミットを進めて、work/Bmasterへマージするプルリクエストを送ったタイミングでようやく、「なぜかwork/Aのコミットが含まれているんだけど?!」…と気付きます。

work/Bにwork/Aのコミットが含まれている樹形図

順当にwork/Awork/Bの順番でmasterへマージできる状況ならばよいですがどうしてもwork/Bの作業「だけ」を急いでマージしたいということもありえますよね。

というわけで、こうなってしまったときの解決方法を調べてみました。

解決方法を2つご紹介

方法1:git rebase --onto

1つ目は作業ブランチの派生元を修正する考え方です。

git rebase --ontoの樹形図

# git rebase --onto {本来親にしたかったブランチ} {間違って親にしてしまったブランチ名} {親を変更する作業ブランチ名}
$ git rebase --onto origin/master work/A work/B

ただしこの方法は「コミットの改変にあたる」点に注意が必要です。
すでにpush済だったブランチの場合、リモートとローカルの履歴が不整合になるため普通にpushしようとしてもエラーになります。-fオプションをつけて強制的にpushさせなければなりません。

$ git push -f origin work/B

自分ひとりで作業しているブランチであればこれで全く問題ないのですが、他の人と共同で作業しているブランチで勝手に過去のコミットを改変するのはNG
方法1を使う際は、この点が確実にクリアできる状況であるときだけにしておきましょう。

方法2:git cherry-pick

方法1が難しければ、新しいブランチを作り直す方法もわかりやすいです。
新しい作業ブランチを正しく作っておき、git cherry-pickを使って欲しいコミットだけを適用します。

git cherry-pickの樹形図

# 正しくブランチを作り直す
$ git checkout -b work/B-new origin/develop
# `work/B`分の作業だけを適用する
# git cherry-pick {コミットID}
$ git cherry-pick abcdef

(おまけ)

冒頭で挙げた他のケースの場合のコマンドも簡単に見てみます。

  • 親にするブランチが古かった(ブランチ作成前にgit fetch originを忘れた)
  • ブランチ作成時、予定と違うブランチを親にしてしまった。作業は進めてしまったが、まだコミットはしていない。

1つ目は至ってシンプルです。git rebase(もしくはmerge)しましょう。

$ git checkout -b origin/master
# この前にgit fetch originするの忘れてた!
$ git fetch origin
$ git rebase origin/master

続いて2つ目。
「親ブランチの指定を忘れたり間違えたりしたまま作業を進めてしまった」ところまでは当記事で取り扱った状況と同じですが、コミット前に気づいたならstashで変更を退避してブランチを正しい状態に直す(git rebaseするか、ブランチを作り直すかする)ことができます。

$ git fetch origin
$ git checkout -b work/A origin/hoge
# origin/masterを親にするつもりがorigin/hogeを親にしてしまった!
# 変更を退避
$ git stash
# 今いるブランチをorigin/masterから派生させる
$ git fetch origin
$ git rebase origin/master
# 変更を適用
$ git stash apply

普段は慣れたコマンドだけを一辺倒に使ってしまうものですが、「もしこの指定を忘れたらどうなるのか?」というのを把握するよいきっかけになりました。
(とはいえ、ブランチの作り方を間違える = 正しい状態から作業がスタートできていない可能性がある…ということなので、まずはブランチの正しい作り方を徹底することが大切ですね…。)

以上、ブランチの派生元を間違えたときの解決方法でした!