[Git]誤ったブランチで実施した変更を正しいブランチに移動する

誤ったブランチで実施した変更を本来の正しいブランチに移動する方法を確認してみました。
2020.04.07

こんにちは、CX事業本部の若槻です。

Git環境での開発作業で、本来はFeatureブランチで作業するべきところを誤ってMasterブランチで作業してしまったということが時々あります。そのような時に誤ったブランチから本来の正しいブランチに変更を移動する方法を確認してみました。

正しいブランチに変更を移動する方法

移動したい変更がどういう状態なのかによって対処方法が変わってきます。今回は以下の方法について確認してみます。

  • 移動したい変更が誤ったブランチ(master)の作業ツリーにある場合
  • 移動したい変更が誤ったブランチ(master)のステージにある場合
  • 移動したい変更が誤ったブランチ(master)でコミット済みの場合
    • 正しいブランチ(feature)を作成済みの場合
    • 正しいブランチ(feature)を未作成の場合

移動したい変更が誤ったブランチ(master)の作業ツリーにある場合

誤ってmasterブランチ上で作業をしてしまったことに変更のステージング前に気付いた、という場合の対処方法です。

git statusするとChanges not staged for commit欄(ファイル新規作成の場合はUntracked files欄)にファイルの変更が登録されており、変更が作業ツリーにあることが分かります。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   Makefile

no changes added to commit (use "git add" and/or "git commit -a")

git stash savemasterブランチで作業ツリー上の変更を退避します。(特定の変更のみ退避したい場合はgit stash save -p

$ git stash save
Saved working directory and index state WIP on master: <HEADのコミット情報>

git stash listで変更がstash上に登録されていることを確認します。

$ git stash list
stash@{0}: WIP on master: <HEADのコミット情報>

git statusするとmasterブランチの作業ツリーから変更が消えていることが分かります。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working tree clean

変更の移動先となるfeatureブランチをチェックアウトします。

$ git checkout feature

退避した変更をgit stash popfeatureブランチに適用(同時にstashから削除)します。変更がfeatureブランチの作業ツリーに登録されます。

$ git stash pop stash@{0}
On branch feature
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   Makefile

no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{0} (e31990220a03acf34021a3dc2d8ae4f934dd84af)
```nges added to commit (use "git add" and/or "git commit -a")

これでmasterブランチからfeatureブランチに作業ツリー上の変更を移動できました。

移動したい変更が誤ったブランチ(master)のステージにある場合

誤ってmasterブランチ上で作業をしてしまったことに変更のステージング後に気付いた、という場合の対処方法です。

git statusするとChanges to be committed欄にファイルの変更が登録されており、変更がステージにあることが分かります。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   Makefile

まずはgit resetmasterブランチのステージ上の変更をHEADの状態に戻します。(特定のファイルの変更のみ戻したい場合はgit reset HEAD -- [ファイル名]

$ git reset HEAD
Unstaged changes after reset:
M       Makefile

git statusすると、移動したい変更が作業ツリーにある状態に戻ったことが分かります。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   Makefile

no changes added to commit (use "git add" and/or "git commit -a")

ここからの対応は変更が作業ツリーにある場合の対応と同じです。

git stash savemasterブランチで作業ツリー上の変更を退避します。(特定の変更のみ退避したい場合はgit stash save -p

$ git stash save
Saved working directory and index state WIP on master: <HEADのコミット情報>

git stash listで変更がstash上に登録されていることを確認します。

$ git stash list
stash@{0}: WIP on master: <HEADのコミット情報>

git statusするとmasterブランチの作業ツリーから変更が消えていることが分かります。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working tree clean

変更の移動先となるfeatureブランチをチェックアウトします。

$ git checkout feature

退避した変更をgit stash popfeatureブランチに適用(同時にstashから削除)します。変更がfeatureブランチの作業ツリーに登録されます。

$ git stash pop stash@{0}
On branch feature
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   Makefile

no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{0} (e31990220a03acf34021a3dc2d8ae4f934dd84af)

これでmasterブランチからfeatureブランチにステージ上の変更を移動できました。

移動したい変更が誤ったブランチ(master)でコミット済みの場合

誤ってmasterブランチ上で作業をしてしまったことにコミット後に気付いた、という場合の対処方法です。

masterブランチ上のm00011からm00013までの3コミットがfeatureブランチに移動したい変更だとします。

$ git log --oneline master
m00013 (HEAD -> master) 移動したい変更3
m00012 移動したい変更2
m00011 移動したい変更1
m00010 (origin/master, origin/HEAD)

正しいブランチ(feature)を作成済みの場合

変更の移動先となるfeatureブランチが既に作成されている場合は、masterからfeatureに変更をマージした上で、masterブランチで変更をリセットすることにより対処可能です。(featuremasterの間で競合する変更が行われた場合など、例外はあります。)

まず変更の移動先となるfeatureブランチをチェックアウトします。

$ git checkout feature

masterブランチ上のコミットをfeatureブランチにマージします。

$ git merge master
Updating f00010..m00013
Fast-forward
 Makefile | 3 +++
 1 file changed, 3 insertions(+)

git logするとmasterブランチ上でコミットした変更がfeatureブランチに移動されていることが分かります。

$ git log --oneline
m00013 (HEAD -> feature, master) 移動したい変更3
m00012 移動したい変更2
m00011 移動したい変更1
f00010 featureの元HEADの変更

次にmasterブランチをチェックアウトします。

$ git checkout master

git reset --hardmasterブランチから移動済みのコミットを削除します。

$ git reset --hard HEAD~3
HEAD is now at m00010

git logすると移動した変更のコミットがmasterブランチから削除されていることが分かります。

$ git log --oneline
m00010 (HEAD -> master, origin/master, origin/HEAD)
m00009
m00008
m00007

これでmasterブランチからfeatureブランチ(作成済み)に変更のコミットを移動できました。

正しいブランチ(feature)が未作成の場合

変更の移動先となるfeatureブランチがまだ作成されていない場合は、featureブランチを作成した上でmasterブランチで変更をリセットするのみで対処可能です。

変更の移動先となるfeatureブランチを作成します。

$ git checkout -b feature

masterブランチをチェックアウトします。

$ git checkout master

git reset --hardmasterブランチから移動したい変更のコミットを削除します。

$ git reset --hard HEAD~3
HEAD is now at m00010

git logすると移動したい変更のコミットがmasterブランチから削除(HEADの位置がリセット)され、featureブランチには残っていることが分かります。

$ git log --oneline --decorate master feature
m00013 (feature) 移動したい変更3
m00012 移動したい変更2
m00011 移動したい変更1
m00010 (HEAD -> master, origin/master, origin/HEAD)

これでmasterブランチからfeatureブランチ(未作成)に変更のコミットを移動できました。

おわりに

誤ったブランチで作業してしまい対処に焦るということを良くやるので、わたし含めた初心者の人ほど転ばず先の杖としてリカバリ方法を心得ておくと安心ですね。

参考