[初心者向け] Gitで間違ったブランチにしたコミットを別ブランチに付け替える

2023.07.07

色々な案件を行ったりきたしていると、 修正ブランチでの作業中に別のブランチを見に行ったまま別案件の作業をやったりして、 ついうっかり別のブランチにコミットをしてしまうようなことがあります。

そんなうっかりさん(俺だ!)のためにブランチの途中からのコミットを 別のブランチに付け替える方法について記録を残しておきます。 できてしまえばとても簡単な処理の組み合わせなので、 これなら次回以降も落ち着いてやれそうです。

やりたいこと

ブランチBにコミットB1〜B4をしていたつもりだったが、 間違ったブランチAの続きにコミットしていた。

実際はこんな感じにしたかった

考え方

コミットを正しい場所に付け直す

今回ブランチが間違っていたので、とにかく新しくブランチを作る必要はありそうです。 そしてそこに、コミット場所を間違っていたコミット群をくっつけていくということが必要です。 コミットが10個以上はあったので、もう一度ファイルを修正ということはやりたくなく、 既存のコミットをそのまま付け替えるというやり方である必要があります。

こういう場合使えるのがcherry-pickです。 cherry-pickは全く別の場所にあるコミットの差分内容を今の場所に持ってくるという処理を行ってくれます。 差分内容という部分を強調しましたが、 Gitは差分で情報を持つのではなく、コミットを行った断面の情報をスナップショット的に持つ仕組みなので、 差分内容を取れるということは多少驚くべきことです。

Gitが差分で情報を持つのではない、ということについては下記の記事を是非ご一読ください。

コミットはスナップショットであり差分ではない

cherry-pickが差分を持って来るという処理であるので、 B1を新しいコミットとしてcherry-pickしてくれば良いということになります。 またそれに続いてB2以降もcherry-pickしてくれば良さそうです。 cherry-pickは複数のコミットを順に持って来る処理を1つのコマンドで実行できますので、 それを使えば大量のコミットの場所を移すということは難しくなさそうです。

コミットメッセージを変更する

私の案件では、コミットメッセージにブランチの名前を書いています。

PRJ_HOGE 1234 関数名を修正

のような感じです。 私はプロジェクト管理ツールにBacklogを使うことが多いのですが、 ブランチ名をチケット番号に合わせていると、これによって自動でリンクが作られるなどのメリットもありますし、 コミット自体には、そのコミットがどのブランチで行われたのかという概念が存在しないため、 その情報を書いておくことは有意義であると思っています。

さて、もちろん私はブランチの番号を完全に勘違いしていたので、 すでにあるコミットのコメントには間違ったブランチ番号が書かれています。 よって、これも正しく直す必要があります。

これについては、rebaseをして直していくのですが、 コミットメッセージのみを修正するという機能がGitには備わっていますので、それを使います。 具体的には、

git rebase -i <コミットハッシュ>

で出て来る画面で reword(または短縮系でrだけ)と指定することで可能です。

やってみた

コマンドラインでやっていきます。

# mainブランチから、正しいブランチBを作る
$ git checkout main
$ git checkout -b feature/B

# cherry-pickでコミット群を持って来る
# feature/Bブランチで
$ git cherry-pick e04f98e 9863538 f3255e2 16ebe81 0d3172b 576a55d b71a65c 2630478 3af4606
# ここでコンフリクトが発生することもあるので、その場合はrebaseするのと同じ感じで解消していく

# コミットメッセージの修正
$ git rebase -i <ブランチBを生やしたmainブランチのコミットハッシュ>

Vimの編集画面が出るので、以下のようにrewordに変更する

reword 34de030 PRJ_HOGE-1234 contextの使い方が間違っていた
reword 39bccb6 PRJ_HOGE-1234 同時実行のチェック機能を追加
reword 88580d9 PRJ_HOGE-1234 プリントなどの整備
reword 9a158a5 PRJ_HOGE-1234 微修正
reword 4675ef5 PRJ_HOGE-1234 通知するように修正
reword 079193b PRJ_HOGE-1234 実行中ステートマシン取得ロジックの修正
reword 2559b07 PRJ_HOGE-1234 イベントメッセージ内の確認を修正
reword fb1d7a0 PRJ_HOGE-1234 コンフリクトステートマシンがなくても動くように
reword 131f79c PRJ_HOGE-1234 エラー文言修正

この状態でVim編集画面を終了すると、 コミットが古いものから順にメッセージ編集画面が表示されるので、一つずつ変更して保存&終了していきます。

蛇足ですが、どうせなら上の画面(rewordに書き換える画面)で一気に変えたいのですが、 どうやらそれはできなさそうです。 やり方探したらできたりするのかもしれませんが、今のところはできない認識です。

ブランチAのお片付け

間違ってコミットしてしまっていたブランチの方も直しておかないといけません。

cherry-pickはコミットを取ってくるのですが、それはコピーであって、 元のコミットがなくなるわけではありません。 (もし元のコミットがなくなってしまうと、古いものから順にcherry-pickすると、 その後ろのものが親無しになってしまう) なので、元のブランチの方は手動で直して上げる必要があります。

やり方としては単純に、戻したいコミットにresetをするだけです。

$ git checkout feature/A
$ git reset --hard <間違ってコミットをする前のコミット>

--hardをつけることで、完全にそのコミット断面に戻ることができます。 Gitはスナップショットで断面を持つので、 git reset --hardでその断面に確実に戻れるということは覚えておいて損はないです。

実は一発でやる方法もある

今回、cherry-pickを使ってコミットを一つずつ持って来る(コマンド的には一発でしたが)方法を行いました。 しかし調べてみると、これは別のコマンドで一発でやることができるようです。 git rebase --ontoを使うことでそれができるようです (コミットメッセージの修正は別にやる必要がある)。

このコマンドは、ブランチの任意のコミット以降を別ブランチとして植え替えるような処理ができます。 植え替えなので、元のブランチを修正するということも不要です。 こちらはまた今度機会があれば紹介させて頂こうと思います。

まとめ

間違ったブランチにコミットを重ねてしまった後に、 それを別ブランチに付け替えるということをやりました。

Gitは何か間違いをしてしまった時、普段やらないような操作をして状態を整えるということが必要になってきて、 ここが本質ではない割に時間がかかってしまいがちです。 しかし、基本的なコマンドを組み合わせることで大抵のことは解決できるはずですので、 まずは基本を抑えることが大事ですね。

最後に git rebase --ontoというのを触りだけ紹介しましたが、 これも、この方法を使わなければいけないわけではないということを理解しているのが大切ですね。 普段の処理を組み合わせたらできるので、変に慣れない方法を使う必要はありません。

何事も基本から。 この気持ちを忘れずに学んでいきたいと思います。

誰かの参考になれば幸いです。