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

A fun metaphor is to think of Git as a timeline management utility. Commits are snapshots of a point in time or points of interest along the timeline of a project's history. Additionally, multiple timelines can be managed through the use of branches. When 'undoing' in Git, you are usually moving back in time, or to another timeline where mistakes didn't happen.

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

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

The whole idea behind any version control system is to store “safe” copies of a project so that you never have to worry about irreparably breaking your code base. Once you’ve built up a project history of commits, you can review and revisit any commit in the history. One of the best utilities for reviewing the history of a Git repository is the git log command. In the example below, we use git log to get a list of the latest commits to a popular open-source graphics library.

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 チュートリアル: 過去のコミットのチェックアウト

Checking out an old file does not move the HEAD pointer. It remains on the same branch and same commit, avoiding a 'detached head' state. You can then commit the old version of the file in a new snapshot as you would any other changes. So, in effect, this usage of git checkout on a file, serves as a way to revert back to an old version of an individual file. For more information on these two modes visit the git checkout page

Viewing an old revision

この例では、常識外れの開発を実験的に開始したものの、それを保存しておくべきか否かについて判断ができないものと仮定します。そしてこの判断の参考とするため、実験的開発を開始する前のプロジェクトの状態を確認するとします。最初に、確認するバージョンの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

This assumes that you're developing on the default master branch. Once you’re back in the master branch, you can use either git revert or git reset to undo any undesired changes.

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

There are technically several different strategies to 'undo' a commit. The following examples will assume we have a commit history that looks like:

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 を使用してコミットを元に戻す方法

Using the git checkout command we can checkout the previous commit, a1e8fb5, putting the repository in a state before the crazy commit happened. Checking out a specific commit will put the repo in a "detached HEAD" state. This means you are no longer working on any branch. In a detached state, any new commits you make will be orphaned when you change branches back to an established branch. Orphaned commits are up for deletion by Git's garbage collector. The garbage collector runs on a configured interval and permanently destroys orphaned commits. To prevent orphaned commits from being garbage collected, we need to ensure we are on a branch.

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

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

Let's assume we are back to our original commit history example. The history that includes the 872fa7e commit. This time let's try a revert 'undo'. If we execute git revert HEAD, Git will create a new commit with the inverse of the last commit. This adds a new commit to the current branch history and now makes it look like:

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 を使用してコミットを元に戻す方法

For this undo strategy we will continue with our working example. git reset is an extensive command with multiple uses and functions. If we invoke git reset --hard a1e8fb5 the commit history is reset to that specified commit. Examining the commit history with git log will now look like:

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 は設定されたシステムエディターを起動し、直前のコミットメッセージを変更できるようにします。新しい変更は、修正されたコミットに追加されます。

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

Before changes are committed to the repository history, they live in the staging index and the working directory. You may need to undo changes within these two areas. The staging index and working directory are internal Git state management mechanisms. For more detailed information on how these two mechanisms operate, visit the git reset page which explores them in depth.

作業ディレクトリ

The working directory is generally in sync with the local file system. To undo changes in the working directory you can edit files like you normally would using your favorite editor. Git has a couple utilities that help manage the working directory. There is the git clean command which is a convenience utility for undoing changes to the working directory. Additionally, git reset can be invoked with the --mixed or --hard options and will apply a reset to the working directory.

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

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

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

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

The preferred method of undoing shared history is git revert. A revert is safer than a reset because it will not remove any commits from a shared history. A revert will retain the commits you want to undo and create a new commit that inverts the undesired commit. This method is safer for shared remote collaboration because a remote developer can then pull the branch and receive the new revert commit which undoes the undesired commit.

まとめ

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

  • Once changes have been committed they are generally permanent
  • Use git checkout to move around and review the commit history
  • git revert は、共有のパブリックな変更を元に戻すときに使用するのが最適
  • git reset は、ローカルのプライベートな変更を元に戻すときに使用するのが最適

In addition to the primary undo commands, we took a look at other Git utilities: git log for finding lost commits git clean for undoing uncommitted changes git add for modifying the staging index.

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