この記事では、Git でコミットを試すさまざまな方法について学びます。
開発者であれば、以前のコミットの 1 つにロールバックしたいが、その方法がわからないという状況に何度も遭遇するでしょう。また、reset、revert、rebase などの Git コマンドを知っていても、それらの違いはわかりません。それでは、始めて、git restart、revert、rebase が何であるかを理解しましょう。
Git リセット
Git reset は複雑なコマンドであり、変更を元に戻すために使用されます。
git restart はロールバック機能と考えることができます。 git restart を使用すると、さまざまなコミット間をジャンプできます。 git restart コマンドの実行には、-soft、-mixed、-hard の 3 つのモードがあります。デフォルトでは、git replace コマンドは混合モードを使用します。 git リセット ワークフローでは、git の 3 つの内部管理メカニズム、つまり HEAD 、 ステージング領域 (インデックス)、および 作業ディレクトリ が関係します。
作業ディレクトリは現在作業している場所であり、ファイルが存在する場所です。 git status コマンドを使用すると、作業ディレクトリに存在するすべてのファイル/フォルダーを確認できます。
ステージング領域 (インデックス) は、git がファイル内のすべての変更を追跡し、保存する場所です。保存された変更は .git ディレクトリに反映されます。 git add “filename” を使用してファイルをステージング領域に追加します。前と同様に、git status を実行すると、ステージング領域にどのファイルが存在するかが表示されます。
Git の現在のブランチは HEAD と呼ばれます。これは、現在のチェックアウト ブランチで発生した最後のコミットを指します。これは、あらゆる参照へのポインタとして扱われます。別のブランチにチェックアウトすると、HEAD も新しいブランチに移動します。
git restart がハード、ソフト、混合モードでどのように機能するかを説明しましょう。ハード モードは、指定されたコミットに移動するために使用され、作業ディレクトリにそのコミットのファイルが設定され、ステージング領域がリセットされます。ソフト リセットでは、ポインタのみが指定されたコミットに変更されます。すべてのコミットのファイルは、リセット前に作業ディレクトリとステージング領域に残ります。混合モード (デフォルト) では、ポインターとステージング領域の両方がリセットされます。
Git ハード リセット
git ハード リセットの目的は、HEAD を指定されたコミットに移動することです。指定されたコミットの後に発生したすべてのコミットを削除します。このコマンドはコミット履歴を変更し、指定されたコミットをポイントします。
この例では、3 つの新しいファイルを追加し、それらをコミットしてからハード リセットを実行します。
以下のコマンドからわかるように、現時点ではコミットするものは何もありません。
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
ここで、3 つのファイルを作成し、それにコンテンツを追加します。
$ vi file1.txt
$ vi file2.txt
$ vi file3.txt
これらのファイルを既存のリポジトリに追加します。
$ git add file*
status コマンドを再実行すると、作成したばかりの新しいファイルが反映されます。
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file:
file1.txt
new file:
file2.txt
new file:
file3.txt
コミットする前に、現在 Git に 3 つのコミットのログがあることをお見せします。
$ git log --oneline
0db602e (HEAD -> master) one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
次に、リポジトリにコミットします。
$ git commit -m 'added 3 files'
[master d69950b] added 3 files
3 files changed, 3 insertions(+)
create mode 100644 file1.txt
create mode 100644 file2.txt
create mode 100644 file3.txt
ls-files を実行すると、新しいファイルが追加されたことがわかります。
$ git ls-files
demo
dummyfile
newfile
file1.txt
file2.txt
file3.txt
git で log コマンドを実行すると、コミットが 4 つあり、HEAD が最新のコミットを指します。
$ git log --oneline
d69950b (HEAD -> master) added 3 files
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
file1.txt を手動で削除して git status を実行すると、変更がコミット用にステージングされていないというメッセージが表示されます。
$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
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:
file1.txt
no changes added to commit (use "git add" and/or "git commit -a")
ここで、ハード リセット コマンドを実行します。
$ git reset --hard
HEAD is now at d69950b added 3 files
ステータスを再確認すると、コミットするものは何もなく、削除したファイルがリポジトリに戻っていることがわかります。ロールバックが発生したのは、ファイルを削除した後にコミットしなかったため、ハード リセット後に前の状態に戻ってしまったためです。
$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
gitのログを確認してみるとこんな感じです。
$ git log
commit d69950b7ea406a97499e07f9b28082db9db0b387 (HEAD -> master)
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 19:53:31 2020 +0530
added 3 files
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 01:04:13 2020 +0530
one more commit
commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 00:54:53 2020 +0530
new commit
commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 00:16:33 2020 +0530
test
ハード リセットの目的は、指定されたコミットをポイントし、作業ディレクトリとステージング領域を更新することです。もう一つ例を示しましょう。現在、コミットの視覚化は次のようになります。
ここでは、HEAD^ を指定してコマンドを実行します。これは、前のコミット (1 つ前のコミット) にリセットすることを意味します。
$ git reset --hard HEAD^
HEAD is now at 0db602e one more commit
ヘッド ポインタが d69950b から 0db602e に変更されたことがわかります。
$ git log --oneline
0db602e (HEAD -> master) one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
ログを確認すると、d69950b のコミットが消えており、ヘッドが 0db602e SHA を指していることがわかります。
$ git log
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14 (HEAD -> master)
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 01:04:13 2020 +0530
one more commit
commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 00:54:53 2020 +0530
new commit
commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 00:16:33 2020 +0530
Test
ls-files を実行すると、ハード リセット後にコミットとそのファイルが削除されたため、file1.txt、file2.txt、および files3.txt がリポジトリになくなっていることがわかります。
$ git ls-files
demo
dummyfile
newfile
Git ソフト リセット
同様に、ソフト リセットの例を示します。上記のように 3 つのファイルを再度追加してコミットしたことを考えてください。以下のように git ログが表示されます。 「ソフト リセット」が私の最新のコミットであり、HEAD もそれを指していることがわかります。
$ git log --oneline
aa40085 (HEAD -> master) soft reset
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
以下のコマンドを使用して、ログ内のコミットの詳細を確認できます。
$ git log
commit aa400858aab3927e79116941c715749780a59fc9 (HEAD -> master)
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 21:01:36 2020 +0530
soft reset
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 01:04:13 2020 +0530
one more commit
commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 00:54:53 2020 +0530
new commit
commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 00:16:33 2020 +0530
test
ここでソフト リセットを使用して、SHA 0db602e085a4d59cfa9393abac41ff5fd7afcb14 を持つ古いコミットの 1 つに切り替えたいと考えています。
そのためには、以下のコマンドを実行します。 6 文字を超える SHA の開始文字を渡す必要があります。完全な SHA は必要ありません。
$ git reset --soft 0db602e085a4
git log を実行すると、HEAD が指定したコミットにリセットされたことがわかります。
$ git log
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14 (HEAD -> master)
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 01:04:13 2020 +0530
one more commit
commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 00:54:53 2020 +0530
new commit
commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <mrgeek@gmail.com>
Date:
Mon May 17 00:16:33 2020 +0530
test
ただし、ここでの違いは、3 つのファイルを追加したコミットのファイル (aa400858aab3927e79116941c715749780a59fc9) がまだ作業ディレクトリにあることです。それらは削除されていません。このため、ハード リセットではなくソフト リセットを使用する必要があります。ソフト モードではファイルを失う危険はありません。
$ git ls-files
demo
dummyfile
file1.txt
file2.txt
file3.txt
newfile
Git を元に戻す
Git では、revert コマンドは、元に戻す操作を実行する、つまり一部の変更を元に戻すために使用されます。これはリセット コマンドに似ていますが、唯一の違いは、新しいコミットを実行して特定のコミットに戻ることです。つまり、 git revert コマンドはコミットであると言っても過言ではありません。
Git revert コマンドは、元に戻す操作の実行中にデータを削除しません。
revert の例として、3 つのファイルを追加し、git commit 操作を実行しているとします。
$ git commit -m 'add 3 files again'
[master 812335d] add 3 files again
3 files changed, 3 insertions(+)
create mode 100644 file1.txt
create mode 100644 file2.txt
create mode 100644 file3.txt
ログには新しいコミットが表示されます。
$ git log --oneline
812335d (HEAD -> master) add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
ここで、過去のコミットの 1 つ、たとえば「59c86c9 新しいコミット」に戻りたいと思います。以下のコマンドを実行します。
$ git revert 59c86c9
これによりファイルが開き、元に戻そうとしているコミットの詳細が表示されます。ここで新しいコミットに名前を付けて、ファイルを保存して閉じることができます。
Revert "new commit"
This reverts commit 59c86c96a82589bad5ecba7668ad38aa684ab323.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Your branch is ahead of 'origin/master' by 4 commits.
# (use "git push" to publish your local commits)
#
# Changes to be committed:
# modified: dummyfile
ファイルを保存して閉じると、次の出力が得られます。
$ git revert 59c86c9
[master af72b7a] Revert "new commit"
1 file changed, 1 insertion(+), 1 deletion(-)
ここで必要な変更を加えるために、リセットとは異なり、revert はもう 1 つの新しいコミットを実行します。ログを再度確認すると、元に戻す操作による新しいコミットが見つかります。
$ git log --oneline
af72b7a (HEAD -> master) Revert "new commit"
812335d add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
Git ログにはコミットのすべての履歴が記録されます。履歴からコミットを削除する場合は、revert は適切な選択ではありませんが、コミットの変更を履歴に保持したい場合は、reset ではなく revert が適切なコマンドです。
Gitリベース
Git では、リベースは、あるブランチのコミットを別のブランチに移動または結合する方法です。開発者として、実際のシナリオでは、master ブランチ上に機能を作成しません。私は自分のブランチ (「フィーチャー ブランチ」) で作業し、機能が追加されたフィーチャー ブランチにいくつかのコミットがあれば、それをマスター ブランチに移動したいと思います。
リベースはマージとよく似ているため、理解するのが少しわかりにくい場合があります。両方をマージしてリベースする目的は、feature ブランチからコミットを取得して、master ブランチまたは他のブランチに配置することです。次のようなグラフがあると考えてください。
あなたが他の開発者とチームで作業しているとします。その場合、他の多くの開発者がさまざまな機能ブランチに取り組んでおり、複数の変更をマージしているため、これが非常に複雑になる可能性があることが想像できます。追跡するのが混乱してしまいます。
したがって、ここでリベースが役立ちます。今回は、git マージを行う代わりに、リベースを行い、2 つの機能ブランチのコミットを取得してマスター ブランチに移動します。リベースは、フィーチャー ブランチからすべてのコミットを取得し、それらをマスター ブランチのコミットの上に移動します。そのため、Git は舞台裏でフィーチャー ブランチのコミットをマスター ブランチに複製しています。
このアプローチにより、すべてのコミットを一列に並べたきれいな直線グラフが得られます。
どのコミットがどこに行ったかを簡単に追跡できます。多くの開発者がいるチームに所属している場合、すべてのコミットがまだ連続していることが想像できるでしょう。そのため、多くの人が同時に同じプロジェクトに取り組んでいる場合でも、非常に簡単に理解できます。
これを実際に見てみましょう。
これが私のマスターブランチの現在の様子です。コミットは 4 つあります。
$ git log --oneline
812335d (HEAD -> master) add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
以下のコマンドを実行して、feature という新しいブランチを作成して切り替えます。このブランチは 2 番目のコミット、つまり 59c86c9 から作成されます。
(master)
$ git checkout -b feature 59c86c9
Switched to a new branch 'feature'
機能ブランチのログを確認すると、マスター (メインライン) からのコミットが 2 つだけあります。
(feature)
$ git log --oneline
59c86c9 (HEAD -> feature) new commit
e2f44fc (origin/master, origin/HEAD) test
機能 1 を作成し、機能ブランチにコミットします。
(feature)
$ vi feature1.txt
(feature)
$ git add .
The file will have its original line endings in your working directory
(feature)
$ git commit -m 'feature 1'
[feature c639e1b] feature 1
1 file changed, 1 insertion(+)
create mode 100644 feature1.txt
もう 1 つの機能、つまり機能 2 を機能ブランチに作成し、コミットします。
(feature)
$ vi feature2.txt
(feature)
$ git add .
The file will have its original line endings in your working directory
(feature)
$ git commit -m 'feature 2'
[feature 0f4db49] feature 2
1 file changed, 1 insertion(+)
create mode 100644 feature2.txt
ここで、feature ブランチのログを確認すると、上で実行した 2 つの新しいコミットが含まれています。
(feature)
$ git log --oneline
0f4db49 (HEAD -> feature) feature 2
c639e1b feature 1
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
ここで、これら 2 つの新しい機能を master ブランチに追加したいと思います。そのために、rebase コマンドを使用します。 feature ブランチから、master ブランチに対してリベースします。これにより、最新の変更に対して機能ブランチが再固定されます。
(feature)
$ git rebase master
Successfully rebased and updated refs/heads/feature.
次に、master ブランチをチェックアウトします。
(feature)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
最後に、feature ブランチに対して master ブランチをリベースします。これにより、これら 2 つの新しいコミットが feature ブランチに取得され、master ブランチ上で再生されます。
(master)
$ git rebase feature
Successfully rebased and updated refs/heads/master.
ここで、master ブランチのログを確認すると、features ブランチの 2 つのコミットが master ブランチに正常に追加されたことがわかります。
(master)
$ git log --oneline
766c996 (HEAD -> master, feature) feature 2
c036a11 feature 1
812335d add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
以上が Git のリセット、リバート、リベースのコマンドについての説明でした。
結論
以上が Git のリセット、リバート、リベースのコマンドについての説明でした。このステップバイステップガイドがお役に立てば幸いです。これで、この記事で説明されているコマンドを使用して、必要に応じてコミットを試す方法がわかりました。