Undoing changes

コミットや変更を元に戻す

このセクションでは、Git の「元に戻す」戦略とコマンドについて説明します。まず重要なことは、Git には、ワープロアプリケーションに備わっているような従来型の「元に戻す」システムがないということです。Git での操作は、従来の「元に戻す」という考え方に当てはめないことをお勧めします。また、Git には話し合いで活用すべき、独自の「元に戻す」操作用語があります。この用語には、リセット、打ち消し、チェックアウト、クリーンアップなどがあります。

Git をタイムライン管理ユーティリティとして考えると、面白いかもしれません。コミットは、プロジェクト履歴のタイムラインに沿った、ある時点または関心のあるポイントのスナップショットです。さらに、ブランチを使えば複数のタイムラインを管理できます。Git で「元に戻す」場合、通常は過去の状態か、間違いが起こらなかった別のタイムラインに移動します。

このチュートリアルでは、ソフトウェア開発プロジェクトの過去のバージョンに関する操作を行う際に必要になるすべてのスキルを学習します。最初に、過去のコミットを調べる方法を示し、次に、公開リポジトリに公開済みのコミットの打ち消しと、ローカルマシン上での未公開のコミットの取り消しの違いを説明します。

間違えた場所を探す:古いコミットを確認する

すべてのバージョン管理システムの背景となっている考え方は、プロジェクトの状態を「安全に」保存し、コードベースに回復不可能な破損を生ずる可能性をなくすということにあります。一旦リポジトリが作成されると、履歴にあるあらゆるコミットのレビューや検討ができるようになります。Git リポジトリの履歴のレビューに最も適したユーティリティの一つは、git log コマンドです。次の例では git log を使用し、人気のオープンソースグラフィックライブラリのために最新コマンドのリストを取得します。

git log --oneline
e2f9a78fe Replaced FlyControls with OrbitControls
d35ce0178 Editor: Shortcuts panel Safari support.
9dbe8d0cf Editor: Sidebar.Controls to Sidebar.Settings.Shortcuts. Clean up.
05c5288fc Merge pull request #12612 from TyLindberg/editor-controls-panel
0d8b6e74b Merge pull request #12805 from harto/patch-1
23b20c22e Merge pull request #12801 from gam0022/improve-raymarching-example-v2
fe78029f1 Fix typo in documentation
7ce43c448 Merge pull request #12794 from WestLangley/dev-x
17452bb93 Merge pull request #12778 from OndrejSpanel/unitTestFixes
b5c1b5c70 Merge pull request #12799 from dhritzkiv/patch-21
1b48ff4d2 Updated builds.
88adbcdf6 WebVRManager: Clean up.
2720fbb08 Merge pull request #12803 from dmarcos/parentPoseObject
9ed629301 Check parent of poseObject instead of camera
219f3eb13 Update GLTFLoader.js
15f13bb3c Update GLTFLoader.js
6d9c22a3b Update uniforms only when onWindowResize
881b25b58 Update ProjectionMatrix on change aspect

コミットごとに一意の SHA-1 識別ハッシュがあります。この ID を使用して、コミットされたタイムラインを移動したり、コミットを再確認したりします。既定では、git log は現在選択されているブランチのコミットのみを表示します。表示したいコミットが別のブランチのものであることは十分に考えられます。git log --branches=* を実行すると、すべてのブランチのすべてのコミットが表示されます。git branch コマンドを使用して、他のブランチの表示やそのブランチへの移動ができます。git branch -a コマンドを実行すると、既知の全ブランチ名が一覧表示されます。表示されたブランチ名のいずれかを git log <branch_name> で使用すると、そのブランチのログを作成できます。

確認したい履歴にコミット参照がある場合は、git checkout コマンドを使用してそのコミットを確認できます。git checkout を使用すると、開発マシン上に保存されたスナップショットを簡単に「ロード」できます。一般的な開発プロジェクトの進行中、HEAD は通常 master ブランチまたはその他のローカルブランチを指しますが、過去のコミットをチェックアウトすると、HEAD はブランチではなく直接コミットを指すようになります。この状態を「detached HEAD」状態と呼び、図で説明すると次のようになります。

Git Tutorial: Checking out a previous commit

過去のファイルをチェックアウトしても、HEAD ポインターは移動しません。同じブランチで同じコミットに留まり、「detached head」の状態になるのを防ぎます。この過去バージョンのファイルは、他の変更と一緒に新しいスナップショットにコミットすることができます。したがって、git checkout コマンドは実質的に個々のファイルの変更を打ち消して元に戻す働きをします。2 つのモードの詳細については、

ページの git checkout を確認してください。

過去のバージョンの閲覧

この例では、かなり実験的な開発を開始したものの、それを保存しておくべきか否かについて判断ができないものと仮定します。そしてこの判断の参考とするため、実験的開発を開始する前のプロジェクトの状態を確認するとします。最初に、確認するバージョンの ID を知る必要があります。

git log --oneline

次のようなプロジェクト履歴が表示されたとします。

b7119f2 Continue doing crazy things
872fa7e Try something crazy
a1e8fb5 Make some important changes to hello.txt
435b61d Create hello.txt
9773e52 Initial import

git checkout コマンドを使用すると、次のようにコミット「Make some import changes to hello.txt」を表示できます。

git checkout a1e8fb5

このコマンドを実行すると、作業ディレクトリはコミット a1e8fb5 とまったく同じ状態になります。この状態で、プロジェクトの現在の状態に影響を与えることなく、ファイルの閲覧、プロジェクトのコンパイル、テストラン、さらにはファイルの編集さえも可能となります。この状態で行われた操作はリポジトリには一切保存されません。開発を続けるにはプロジェクトの「現在の」状態に戻る必要があります。

git checkout master

ここでは、デフォルトである master ブランチで開発作業をしているものと仮定しています。master ブランチに戻った後、git revert または git reset コマンドを使用して、不要と判断した変更を元に戻すことができます。

コミットしたスナップショットを元に戻す

実際には、コミットを「元に戻す」ための戦略は複数あります。次の例のようなコミット履歴があるとします。

git log --oneline
872fa7e Try something crazy
a1e8fb5 Make some important changes to hello.txt
435b61d Create hello.txt
9773e52 Initial import

コミット 872fa7e Try something crazy を元に戻すことに焦点を当てます。少しおかしなことが起きるかもしれません。

git checkout を使用してコミットを元に戻す方法

git checkout コマンドを使用すると、以前のコミット a1e8fb5 をチェックアウトして、コミットがおかしくなる前の状態にリポジトリを戻すことができます。特定のコミットをチェックアウトすると、リポジトリは「detached HEAD」状態になります。これはブランチ上で作業していないことを意味します。detached 状態で作成されたコミットは、ブランチを確立されたブランチに戻すよう変更したときにどれも孤立した状態になります。孤立したコミットは Git のガベージコレクションの削除対象となります。ガベージコレクションは設定されたインターバルで実行され、孤立したコミットは完全に削除されます。孤立したコミットがガベージコレクションの対象となることを防ぐには、必ずブランチで作業する必要があります。

detached HEAD 状態から、git checkout -b new_branch_without_crazy_commit を実行できます。この操作により、new_branch_without_crazy_commit というブランチが新規作成され、この状態に切り替わります。リポジトリは新しい履歴タイムライン上にあります。このタイムラインにコミット 872fa7e は存在しません。この時点で、「元に戻し終わった」と見なし、コミット 872fa7e がないこの新しいブランチで作業を継続できます。残念ながら、以前のブランチが必要な場合、おそらくそれは master ブランチに存在していたため、この「元に戻す」戦略は適していません。他の「元に戻す」戦略を見ていきましょう。詳しい情報や例については、git checkout の説明を参照してください。

git revert を使用して、パブリックのコミットを元に戻す方法

最初のコミット履歴例に戻ったものとします。この履歴にはコミット 872fa7e が含まれます。今回は「元に戻す」の打ち消しを試します。git revert HEAD を実行すると、Git は前回のコミットの逆向きとなる新しいコミットを作成します。これにより、新しいコミットは現在のブランチ履歴に追加され、次の状態になります。

git log --oneline
e2f9a78 Revert "Try something crazy"
872fa7e Try something crazy
a1e8fb5 Make some important changes to hello.txt
435b61d Create hello.txt
9773e52 Initial import

この時点で、実際にはコミット 872fa7e を再度元に戻しています。872fa7e はまだ履歴に存在しますが、新しいコミット e2f9a78872fa7e の逆向きのコミットです。前回のチェックアウト戦略とは異なり、同じブランチを使用し続けることができます。このソリューションは元に戻す方法として満足度が高く、パブリック共有リポジトリを使用する際の理想的な「元に戻す」方法です。Git 履歴を厳選した最低限の状態に保つ要件がある場合、この戦略は適さない場合もあります。

git reset を使用してコミットを元に戻す方法

この元に戻す戦略に関して、実際の例をさらに紹介します。git reset は複数の使用法と機能を備えた幅広いコマンドです。git reset --hard a1e8fb5 を起動すると、コミット履歴が特定のコミットにリセットされます。git log のコミット履歴を確認すると、次のようになります。

git log --oneline
a1e8fb5 Make some important changes to hello.txt
435b61d Create hello.txt
9773e52 Initial import

ログ出力から、コミット e2f9a78872fa7e がコミット履歴からなくなったことがわかります。ここで、「crazy」なコミットは一切なかったかのように新しいコミットを作成し、作業を続けることができます。変更を元に戻すこの方法には、履歴をクリーンアップする効果があります。ローカルの変更に対してはリセットの実行が優れていますが、共有リモートリポジトリで作業している場合はより複雑になります。共有リモートリポジトリにコミット 872fa7e をプッシュしている場合、履歴をリセットしたブランチの git push を実行すると、Git がこれを検知してエラーにします。Git は、プッシュしようとしているブランチに足りないコミットがあるため、最新ではないと判断します。このような場合は、git revert を使用して元に戻すことをお勧めします。

直前のコミットを元に戻す

前のセクションでは、コミットを元に戻すためのさまざまな戦略について説明しました。これらの戦略はすべて、直前に実行したコミットにも適用できます。ただし、場合によっては、直前のコミットの削除やリセットが不要なこともあります。おそらくコミットが不完全だっただけです。そのような場合は、直前のコミットを修正します。作業ディレクトリでさらに変更を加え、git add を使用してコミットのためにステージングしたら、git commit --amend を実行します。こうすることで、Git は設定されたシステムエディターを起動し、直前のコミットメッセージを変更できるようにします。新しい変更は、修正されたコミットに追加されます。

コミットされていない変更を元に戻す

リポジトリの履歴にコミットするまで、変更はステージングインデックスと作業ディレクトリに保管されています。これら 2 つのエリア内の変更を元に戻すことができます。ステージングインデックスと作業ディレクトリは、内部的な Git の状態管理メカニズムです。これら 2 つのメカニズムの仕組みについては、git reset ページの説明を参照してください。

作業ディレクトリ

通常、作業ディレクトリはローカルのファイルシステムと同期しています。作業ディレクトリの変更を元に戻すには、使い慣れたエディターで普段行うようにファイルを編集します。Git には、作業ディレクトリの管理に役立つユーティリティがいくつか用意されています。git clean コマンドは、作業ディレクトリへの変更を元に戻すのに便利なユーティリティです。さらに、git reset--mixed または --hard オプションを使用して実行すると、作業ディレクトリにリセットが適用されます。

ステージングインデックス

git add コマンドは、ステージングインデックスに変更を追加するために使用します。ステージングインデックスの変更を元に戻すには、主に git reset を使用します。--mixed を使用したリセットは、保留中の変更をステージングインデックスから作業ディレクトリに移します。

パブリックな変更の取り消し

リモートリポジトリを使ってチームで作業する場合、変更を元に戻すときにはさらに考慮が必要です。通常、git reset は「ローカル」で元に戻す方法と見なすべきです。プライベートブランチへの変更を元に戻す場合は、リセットの使用をお勧めします。リセットでは、他の開発者が作業している可能性がある他のブランチからコミットの取り消しを切り離すことができます。共有ブランチでリセットを実行した後、git push を使ってそのブランチをリモートでプッシュすると、問題が生じます。この場合 Git は、プッシュしようとしているブランチに足りないコミットがあるため、リモートブランチより古いと判断してプッシュをブロックします。

共有履歴を元に戻す方法としては、git revert をお勧めします。打ち消しは共有履歴からコミットを削除することは一切ないため、リセットよりも安全です。打ち消しは元に戻したいコミットを保持したまま、削除したいコミットを逆向きにする新しいコミットを作成します。離れた場所にいる開発者がブランチをプルして、やり直したいコミットを元に戻した新しい打ち消しコミットを受け取ることができるため、この方法は共有リモートコラボレーションにより安全な選択肢です。

概要

ここまでは、Git で作成したものを元に戻すためのさまざまな戦略の概要を説明しました。Git プロジェクトにおいて「元に戻す」方法は 1 つではないことを覚えておいてください。このページで説明したトピックのほとんどは、関連する Git コマンド別のページでさらに詳しく説明されています。最もよく使われている「元に戻す」ツールは、git checkoutgit revertgit reset です。覚えておくべきいくつかの重要ポイントは次のとおりです。

  • 変更をコミットした時点で、基本的にはその変更を変えることはできなくなる
  • git checkout を使用すれば、コミット履歴の項目間での移動や確認が可能
  • git revert は、共有のパブリックな変更を元に戻すときに使用するのに最適
  • git reset は、ローカルでのプライベートな変更を元に戻すときに使用するのが最適

元に戻すための基本的なコマンドに加え、他の Git ユーティリティも確認しました (失われたコマンドを見つけ出す git log、コミットされていない変更を元に戻す git clean、ステージングインデックスを修正するgit add)。

これらのコマンドは、それぞれ詳しく説明されています。ここで説明した個々のコマンドの詳細については、対応するリンク先を参照してください。