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

Git checkout

The git checkout command serves three distinct functions: checking out files, checking out commits, and checking out branches. In this module, we’re only concerned with the first two configurations.

コミットのチェックアウトを行うと、作業ディレクトリがそのコミットと完全に一致した状態になります。このコマンドは、プロジェクトの現在の状態を一切変更することなく過去の状態を確認する場合に使用します。ファイルのチェックアウトを行うと、作業ディレクトリの他の部分に一切影響を与えることなくそのファイルの過去のリビジョンを確認することができます。

使用法

git checkout master

master ブランチに戻るコマンドです。ブランチについては次の章で詳しく説明しますが、ここではとりあえず master ブランチはプロジェクトの「現在の」状態に戻る手段だと考えてください。

git checkout <commit> <file>

Check out a previous version of a file. This turns the <file> that resides in the working directory into an exact copy of the one from <commit> and adds it to the staging area.

git checkout <commit>

Update all files in the working directory to match the specified commit. You can use either a commit hash or a tag as the <commit> argument. This will put you in a detached HEAD state.

ディスカッション

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, git checkout is an easy way to “load” any of these saved snapshots onto your development machine.

Checking out an old commit is a read-only operation. It’s impossible to harm your repository while viewing an old revision. The “current” state of your project remains untouched in the master branch (see the Branches Module for details). During the normal course of development, the HEAD usually points to master or some other local branch, but when you check out a previous commit, HEAD no longer points to a branch—it points directly to a commit. This is called a “detached HEAD” state, and it can be visualized as the following:

Git チュートリアル: 過去のコミットのチェックアウト

On the other hand, checking out an old file does affect the current state of your repository. You can re-commit the old version in a new snapshot as you would any other file. So, in effect, this usage of git checkout serves as a way to revert back to an old version of an individual file.

Git トレーニング: ファイルの過去のバージョンのチェックアウト

過去のバージョンの閲覧

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

git log --oneline

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

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

You can use git checkout to view the “Make some import changes to hello.py” commit as follows:

git checkout a1e8fb5

This makes your working directory match the exact state of the a1e8fb5 commit. You can look at files, compile the project, run tests, and even edit files without worrying about losing the current state of the project. Nothing you do in here will be saved in your repository. To continue developing, you need to get back to the “current” state of your project:

git checkout master

ここでは、デフォルトである master ブランチで開発作業をしているものと仮定しています (master ブランチについては、ブランチ の章で詳しく説明します)。

Once you’re back in the master branch, you can use either git revert or git reset to undo any undesired changes.

ファイルのチェックアウト

If you’re only interested in a single file, you can also use git checkout to fetch an old version of it. For example, if you only wanted to see the hello.py file from the old commit, you could use the following command:

git checkout a1e8fb5 hello.py

Remember, unlike checking out a commit, this does affect the current state of your project. The old file revision will show up as a “Change to be committed,” giving you the opportunity to revert back to the previous version of the file. If you decide you don’t want to keep the old version, you can check out the most recent version with the following:

git checkout HEAD hello.py

Git revert

The git revert command undoes a committed snapshot. But, instead of removing the commit from the project history, it figures out how to undo the changes introduced by the commit and appends a new commit with the resulting content. This prevents Git from losing history, which is important for the integrity of your revision history and for reliable collaboration.

Git チュートリアル: git の打消し

使用法

git revert <commit>

Generate a new commit that undoes all of the changes introduced in <commit>, then apply it to the current branch.

ディスカッション

Reverting should be used when you want to remove an entire commit from your project history. This can be useful, for example, if you’re tracking down a bug and find that it was introduced by a single commit. Instead of manually going in, fixing it, and committing a new snapshot, you can use git revert to automatically do all of this for you.

打消しと取消し

It's important to understand that git revert undoes a single commit—it does not "revert" back to the previous state of a project by removing all subsequent commits. In Git, this is actually called a reset, not a revert.

Git チュートリアル: 打消しと取消し

Reverting has two important advantages over resetting. First, it doesn’t change the project history, which makes it a “safe” operation for commits that have already been published to a shared repository. For details about why altering shared history is dangerous, please see the git reset page.

Second, git revert is able to target an individual commit at an arbitrary point in the history, whereas git reset can only work backwards from the current commit. For example, if you wanted to undo an old commit with git reset, you would have to remove all of the commits that occurred after the target commit, remove it, then re-commit all of the subsequent commits. Needless to say, this is not an elegant undo solution.

The following example is a simple demonstration of git revert. It commits a snapshot, then immediately undoes it with a revert.

# Edit some tracked files
# Commit a snapshot
git commit -m "Make some changes that will be undone"
# Revert the commit we just created
git revert HEAD

これは、次のように視覚化できます。

Git チュートリアル: git revert の例

Note that the 4th commit is still in the project history after the revert. Instead of deleting it, git revert added a new commit to undo its changes. As a result, the 3rd and 5th commits represent the exact same code base, and the 4th commit is still in our history just in case we want to go back to it down the road.

Git のリセット

If git revert is a “safe” way to undo changes, you can think of git reset as the dangerous method. When you undo with git reset(and the commits are no longer referenced by any ref or the reflog), there is no way to retrieve the original copy—it is a permanent undo. Care must be taken when using this tool, as it’s one of the only Git commands that has the potential to lose your work.

Like git checkout, git reset is a versatile command with many configurations. It can be used to remove committed snapshots, although it’s more often used to undo changes in the staging area and the working directory. In either case, it should only be used to undo local changes—you should never reset snapshots that have been shared with other developers.

使用法

git reset <file>

作業ディレクトリに何の変更も加えずに、指定したファイルをステージングエリアから削除するコマンドです。このコマンドを実行すると、変更を書き込むことなく指定したファイルをアンステージします。

Git のリセット

Reset the staging area to match the most recent commit, but leave the working directory unchanged. This unstages all files without overwriting any changes, giving you the opportunity to re-build the staged snapshot from scratch.

git reset --hard

Reset the staging area and the working directory to match the most recent commit. In addition to unstaging changes, the --hard flag tells Git to overwrite all changes in the working directory, too. Put another way: this obliterates all uncommitted changes, so make sure you really want to throw away your local developments before using it.

git reset <commit>

Move the current branch tip backward to <commit>, reset the staging area to match, but leave the working directory alone. All changes made since <commit> will reside in the working directory, which lets you re-commit the project history using cleaner, more atomic snapshots.

git reset --hard <commit>

Move the current branch tip backward to <commit> and reset both the staging area and the working directory to match. This obliterates not only the uncommitted changes, but all commits after <commit>, as well.

ディスカッション

All of the above invocations are used to remove changes from a repository. Without the --hard flag, git reset is a way to clean up a repository by unstaging changes or uncommitting a series of snapshots and re-building them from scratch. The --hard flag comes in handy when an experiment has gone horribly wrong and you need a clean slate to work with.

Whereas reverting is designed to safely undo a public commit, git reset is designed to undo local changes. Because of their distinct goals, the two commands are implemented differently: resetting completely removes a changeset, whereas reverting maintains the original changeset and uses a new commit to apply the undo.

Git チュートリアル: 打消しと取消し

公開済み履歴の取り消しは厳禁

You should never use git reset <commit> when any snapshots after <commit> have been pushed to a public repository. After publishing a commit, you have to assume that other developers are reliant upon it.

Removing a commit that other team members have continued developing poses serious problems for collaboration. When they try to sync up with your repository, it will look like a chunk of the project history abruptly disappeared. The sequence below demonstrates what happens when you try to reset a public commit. The origin/master branch is the central repository’s version of your local master branch.

Git チュートリアル: 公開済み履歴の取り消し

As soon as you add new commits after the reset, Git will think that your local history has diverged from origin/master, and the merge commit required to synchronize your repositories is likely to confuse and frustrate your team.

The point is, make sure that you’re using git reset <commit> on a local experiment that went wrong—not on published changes. If you need to fix a public commit, the git revert command was designed specifically for this purpose.

ファイルのアンステージ

The git reset command is frequently encountered while preparing the staged snapshot. The next example assumes you have two files called hello.py and main.py that you’ve already added to the repository.

# Edit both hello.py and main.py
# Stage everything in the current directory
git add .
# Realize that the changes in hello.py and main.py
# should be committed in different snapshots
# Unstage main.py
git reset main.py
# Commit only hello.py
git commit -m "Make some changes to hello.py"
# Commit main.py in a separate snapshot
git add main.py
git commit -m "Edit main.py"

As you can see, git reset helps you keep your commits highly-focused by letting you unstage changes that aren’t related to the next commit.

ローカルなコミットの削除

次の例はより高度なユースケースを示します。ここでは実験的開発をしばらく行っていると仮定し、いくつかのスナップショットをコミットした後でそれらをすべて破棄する場合にどうするべきかを示します。

# Create a new file called `foo.py` and add some code to it
# Commit it to the project history
git add foo.py
git commit -m "Start developing a crazy feature"
# Edit `foo.py` again and change some other tracked files, too
# Commit another snapshot
git commit -a -m "Continue my crazy feature"
# Decide to scrap the feature and remove the associated commits
git reset --hard HEAD~2

The git reset HEAD~2 command moves the current branch backward by two commits, effectively removing the two snapshots we just created from the project history. Remember that this kind of reset should only be used on unpublished commits. Never perform the above operation if you’ve already pushed your commits to a shared repository.

git clean

The git clean command removes untracked files from your working directory. This is really more of a convenience command, since it’s trivial to see which files are untracked with git status and remove them manually. Like an ordinary rm command, git clean is not undoable, so make sure you really want to delete the untracked files before you run it.

The git clean command is often executed in conjunction with git reset --hard. Remember that resetting only affects tracked files, so a separate command is required for cleaning up untracked ones. Combined, these two commands let you return the working directory to the exact state of a particular commit.

使用法

git clean -n

git clean の「予行演習」を行うコマンドです。このコマンドを実行すると削除されるファイルを表示しますが、実際の削除は行われません。

git clean -f

Remove untracked files from the current directory. The -f (force) flag is required unless the clean.requireForce configuration option is set to false (it's true by default). This will not remove untracked folders or files specified by .gitignore.

git clean -f <path>

追跡対象外のファイルを削除しますが、その対象範囲は指定したパスに限定するコマンドです。

git clean -df

Remove untracked files and untracked directories from the current directory.

git clean -xf

カレントディレクトリ内の追跡対象外ファイルおよび Git では通常無視されるファイルを削除します。

ディスカッション

The git reset --hard and git clean -f commands are your best friends after you’ve made some embarrassing developments in your local repository and want to burn the evidence. Running both of them will make your working directory match the most recent commit, giving you a clean slate to work with.

The git clean command can also be useful for cleaning up the working directory after a build. For example, it can easily remove the .o and .exe binaries generated by a C compiler. This is occasionally a necessary step before packaging a project for release. The -x option is particularly convenient for this purpose.

Keep in mind that, along with git reset, git clean is one of the only Git commands that has the potential to permanently delete commits, so be careful with it. In fact, it’s so easy to lose important additions that the Git maintainers require the -f flag for even the most basic operations. This prevents you from accidentally deleting everything with a naive git clean call.

次の例は、作成された新規ファイルを含めて作業ディレクトリ内のすべての変更を取り消すものです。ここでは、既にいくつかのスナップショットをコミットしており、また新たな実験的開発の途中にあると仮定しています。

# 既存のファイルの編集
# 新規ファイルの追加
# 自分が何をしているのかが分からないことを認識する

# 追跡対象ファイルの変更を元に戻す
git reset --hard

# 追跡対象外のファイルの削除
git clean -df

After running this reset/clean sequence, the working directory and the staging area will look exactly like the most recent commit, and git status will report a clean working directory. You're now ready to begin again.

Note that, unlike the second example in git reset, the new files were _not _added to the repository. As a result, they could not be affected by git reset --hard, and git clean was required to delete them.