Git を利用していると、編集内容を元に戻したい、コミットを取り消したい、などの状況がでてくるかと思います。
ここでは Git 操作の取り消し方や戻し方を解説いたします。
参考までに、ソース修正から本番リリースまでの流れを説明した以前の記事をご確認ください。
基本的にこの流れの中で発生しうるもののみを解説していきます。
ローカルブランチでの編集作業の取り消し(addする前)
ローカルブランチでの作業をすべてなかったことにしたい、あるいは特定のファイルだけ元に戻したいという場合はgit checkout
コマンドを利用します。
# すべて元に戻す
>git checkout .
Updated 2 paths from the index
# 指定のファイルだけ元に戻す
>git checkout {filename}
なお、ファイル追加という編集作業は本コマンドでは元に戻りませんので、git clean
コマンドを利用します。
# 再帰的にディレクトリを辿って削除を行う
>git clean -df
Removing subdir/
Removing work.html
git add の取り消しgit add
を取り消したい場合はgit reset
コマンドを利用します。
取り消されるのはgit add
だけですので、編集内容も元に戻したいのであれば本作業後に上記の「ローカルブランチでの編集作業の取り消し」を行います。
# git addしたすべてのファイルを取り消し
>git reset
Unstaged changes after reset:
D remove.html
M update.html
# 指定のファイルのみ取り消し
>git reset {filename}
ローカルブランチでのコミットの取り消し
ワークフロー:手順 ②
コミットしたけどすぐに修正の誤りに気付いたときなど、コミットを取り消したい場合もgit reset
コマンドでできます。
オプションによって編集の内容を残したり、git add
も取り消したりできます。
# 直前のコミットを取り消し(git addも編集内容も残る)
>git reset --soft HEAD^
# 直前のコミットを取り消し(git addは消去、編集内容は残る)
>git reset --mixed HEAD^
Unstaged changes after reset:
D remove.html
M update.html
# 直前のコミットを取り消し(git addも編集内容も消去)
>git reset --hard HEAD^
HEAD is now at 0f35ffd First Commit
# 指定のコミットまで戻す(指定コミットまでが残る)
>git reset --soft/mixed/hard {復元するコミットID}
git reset
を間違ってしまった、取り消したい!、という場合もgit reset
です。
復元したい状態のコミット ID を指定してリセットします。
直前のgit reset
であれば ORIG_HEAD が使えます。
# 直前のgit resetを取り消す
>git reset --hard ORIG_HEAD
コミット ID の検索は、素のgit log
コマンドよりもgit reflog
コマンドや--graph
オプションをつけたgit log
コマンドの方が見やすいかと思います。
>git reflog
54d6f49 (HEAD -> develop, issue01) HEAD@{0}: reset: moving to 54d6f49 # ←ここをなかったことにしたい
02d1534 HEAD@{1}: merge issue01: Merge made by the 'recursive' strategy.
0f35ffd (origin/master, origin/develop, master) HEAD@{2}: checkout: moving from issue01 to develop
54d6f49 (HEAD -> develop, issue01) HEAD@{3}: commit: issue01をコミット
0f35ffd (origin/master, origin/develop, master) HEAD@{4}: checkout: moving from develop to issue01
0f35ffd (origin/master, origin/develop, master) HEAD@{5}: checkout: moving from master to develop
0f35ffd (origin/master, origin/develop, master) HEAD@{6}: commit (initial): First Commit
なお、すでにリモートブランチにマージされたコミットは、このような方法でリセットしてはいけません。git reset
コマンドはコミットそのものがなかったことになってしまうため、ログに残らなくなってしまうのです。
当該コミットに影響が及ぶ開発者は、コミットが消えたことになかなか気づけずに苦労することでしょう。
このような場合には後述のgit revert
コマンドを利用した方が良いかと思います。
リモートにあがったコミットのログは第三者の目に触れた「周知の事実」となりますので勝手に変えないよう注意しましょう。
ローカルブランチ間のマージの取り消し
ワークフロー:手順 ④
ローカルのブランチ同士のマージであれば、これもgit reset
で取り消します。
マージの取り消しなので、オプションは--hard
が良いでしょう。
# 直前のマージの取り消し
>git reset --hard HEAD^
HEAD is now at 0f35ffd First Commit
# 指定のコミットまで戻す(指定コミットまでが残る)
>git reset --hard {復元するコミットID}
マージの際にコンフリクトが発生した場合の取り消し
ワークフロー:手順 ④
ローカルのdevelop
ブランチへのマージの際にはコンフリクトが発生する場合があります。
このような場合は--abort
オプションですぐに取り消せます。
# コンフリクトの発生したマージの取り消し
>git merge --abort
HEAD is now at 0f35ffd First Commit
リモートブランチへの push の取り消し
ワークフロー:手順 ⑤
すでに言いましたが、リモートブランチにマージされたコミットはリセットしてはいけません。
ここでは他の開発者にも何を行ったかが明確になるように、git revert
コマンドを利用します。git revert
コマンドは、指定されたコミットを打ち消すコミットを実行します。
これをpush
することで前回のpush
が取り消された状態になります。
もちろんブランチを手作業で元に戻してコミットし、push
を行っても構いません。
# マージコミットの打ち消し
>git revert -m 1 {マージコミットのコミットID}
Removing add/add.html
[develop c244c29] Revert "Merge branch 'issue01' into develop"
3 files changed, 1 insertion(+), 2 deletions(-)
delete mode 100644 add/add.html
create mode 100644 remove.html
# リモートブランチへpush
>git push origin develop
Enumerating objects: 6, done.
なおマージの際には保守性の観点から--no-ff
オプションをつけることをお勧めします。
コミットツリーで見たときにマージの箇所が見やすくなります。
# issue01ブランチをdevelopブランチにマージする
# --no-ff: fast fowardを抑制しマージコミットを作る
>git checkout develop
>git merge --no-ff issue01
>git log --graph --all --format='%h -%d %s'
* 02d1534 - (HEAD -> develop) Merge branch 'issue01' into develop # ★マージコミット
|\
| * 54d6f49 - (issue01) issue01をコミット
|/
* 0f35ffd - (origin/master, origin/develop, master) First Commit
--no-ff
オプションを指定してマージコミットを作成したため、このコミットに向けてgit revert
することでマージ作業を取り消すことができました。
# マージコミットの打ち消し
>git revert -m 1 02d1534
# c244c29(▲)が打ち消しのコミット(★がマージコミット)
>git reflog
c244c29 (HEAD -> develop, origin/develop) HEAD@{0}: revert: Revert "Merge branch 'issue01' into develop" ▲
02d1534 (origin/develop) HEAD@{1}: merge issue01: Merge made by the 'recursive' strategy. ★
0f35ffd (origin/master, master) HEAD@{2}: checkout: moving from issue01 to develop
54d6f49 (issue01) HEAD@{3}: commit: issue01をコミット
>git log --graph --all --format='%h -%d %s'
* c244c29 - (HEAD -> develop, origin/develop) Revert "Merge branch 'issue01' into develop" ▲
* 02d1534 - (origin/develop) Merge branch 'issue01' into develop ★
|\
| * 54d6f49 - (issue01) issue01をコミット
|/
* 0f35ffd - (origin/master, master) First Commit
マージコミットをgit revert
する場合は、-m
オプションでどちらのブランチの状態に戻すのか指定する必要があります。
今回は取り消し作業なので1を指定します。
>git revert 02d1534
error: commit 02d1534421f9ae4b80168e537f6b2f9cf8446eba is a merge but no -m option was given.
fatal: revert failed
# マージ前の状態にする場合
>git revert -m 1 02d1534
# マージ後の状態にする場合
>git revert -m 2 02d1534
一時的な編集内容の退避
Git ではブランチを移動してもコミットしないかぎりは編集内容(git add
も含む)が持ち越されてしまいます。
ブランチを複数掛け持ちながら作業したい場合、特に緊急の障害修正があった場合など、今の編集内容をどこかに退避させたい場面があるかと思います。
>git checkout -b issue02
~ 編集作業 ~
>git checkout issue01
D remove.html
M update.html
Switched to branch 'issue01'
>git status
On branch issue01
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: remove.html
modified: update.html
Untracked files:
(use "git add <file>..." to include in what will be committed)
add/
no changes added to commit (use "git add" and/or "git commit -a")
このような場合にはgit stash
コマンドを利用します。-u
オプションを指定することにより、新規のファイル追加も退避することができます。
>git stash save "コメント内容" -u
Saved working directory and index state On issue02: コメント内容
これでブランチが編集前の状態に戻り、他のブランチへ移動しても大丈夫になりました。
>git status
On branch issue02
nothing to commit, working tree clean
>git checkout develop
Switched to branch 'develop'
Your branch is up to date with 'origin/develop'.
git stash
で退避したブランチはlist
サブコマンドで一覧を見ることができます。
>git stash list
stash@{0}: On issue02: コメント内容
stash@{1}: On issue02: 退避2
stash@{2}: On issue01: 退避1
リスト番号を指定して退避を戻します。git stash apply
は退避したブランチをリストに残したままにします。git stash pop
は退避したブランチをリストから削除します。
>git stash apply 0
Removing remove.html
On branch issue01
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted: remove.html
modified: update.html
Untracked files:
(use "git add <file>..." to include in what will be committed)
add/
no changes added to commit (use "git add" and/or "git commit -a")
退避リストの削除にはdrop
やclear
サブコマンドがあります。
# リスト番号を指定して特定の退避をリストから削除
>git stash drop 1
Dropped refs/stash@{1} (51173bc08f522d26adaea5681e3de03204c9368f)
>git stash list
stash@{0}: On issue02: コメント内容
stash@{2}: On issue01: 退避1
# リストの全削除
>git stash clear
参考: Git を利用した開発環境・テスト環境・本番環境の構成
参考: Git コンフリクトの解消
参考: Git のフックを利用したデプロイの方法
参考: git push を Chatwork や Slack へ通知する方法
コメント