git reset & git checkout 使い分けまとめ

"作業状態を戻す"用途での使い分けを整理してみました。
2020.04.23

こんにちは、CX事業本部の若槻です。

Gitコマンドのgit resetgit checkoutは、いずれも「インデックス(ステージ)」や「作業ツリー」や「HEAD」などを指定の状態に戻すことができるコマンドです。今回はこのgit resetgit checkoutをどんなときにどう使えばよいか?を整理してみました。

git helpを見てみる

まず、各コマンドのgit helpを見てみます。

git resetについて

$ git reset --help

NAME
       git-reset - Reset current HEAD to the specified state

SYNOPSIS
       git reset [-q] [<tree-ish>] [--] <paths>...
       git reset (--patch | -p) [<tree-ish>] [--] [<paths>...]
       git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]

DESCRIPTION
       In the first and second form, copy entries from <tree-ish> to the index. In the third form, set the current branch head (HEAD) to <commit>, optionally modifying index and working tree to match. The <tree-ish>/<commit> defaults to HEAD in all forms.

要約するとgit resetは主に以下の操作ができるようです。

  1. 現在のブランチのHEADからステージへエントリーをコピーし、オプションで作業ツリーへエントリーをコピーする。
  2. 現在のブランチのHEADを指定のコミットに移動し、オプションで移動先のコミットからステージと作業ツリーへエントリーをコピーする。

git checkoutについて

$ git checkout --help

NAME
       git-checkout - Switch branches or restore working tree files

SYNOPSIS
       git checkout [-q] [-f] [-m] [<branch>]
       git checkout [-q] [-f] [-m] --detach [<branch>]
       git checkout [-q] [-f] [-m] [--detach] <commit>
       git checkout [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
       git checkout [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
       git checkout [<tree-ish>] [--] <pathspec>...
       git checkout (-p|--patch) [<tree-ish>] [--] [<paths>...]

DESCRIPTION
       Updates files in the working tree to match the version in the index or the specified tree. If no paths are given, git checkout will also update HEAD to set the specified branch as the current branch.

要約するとgit checkoutは主に以下の操作ができるようです。

  1. 作業ツリーのファイルをステージまたは指定されたツリーのバージョンと一致させる。pathspecが指定されていない場合、HEADを指定されたブランチに設定する。

どんなときにどのコマンドを使えば良いか整理してみた

どんなとき(何を何の状態で戻したいとき)にどのコマンドを使えば良いか、大きく以下の二通りに分けて整理してみました。

  • HEADまたはステージから戻す場合
  • 過去のコミットから戻す場合

HEADまたはステージから戻す場合

1. ステージから作業ツリーに特定のファイルを戻す

この場合はgit checkout -- <ファイル>コマンドを利用します。

利用例

ファイルaaa.txtbbb.txtがステージに追加され、さらに作業ツリーで変更されています。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   aaa.txt
        new file:   bbb.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   aaa.txt
        modified:   bbb.txt

git checkout -- aaa.txtを実行します。

$ git checkout -- aaa.txt

ステージから作業ツリーにファイルaaa.txtのみ戻りました。(aaa.txtのステージ未追加の変更が消された)

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   aaa.txt
        new file:   bbb.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   bbb.txt

ちなみにステージにないファイルをチェックアウトしようとするとエラーとなります。

$ git checkout -- ddd.txt
error: pathspec 'ddd.txt' did not match any file(s) known to git.

2. ステージから作業ツリーにすべてのファイルを戻す

この場合はgit checkout .コマンドを利用します。

利用例

ファイルaaa.txtbbb.txtの変更がステージに追加され、さらに作業ツリーで変更されています。

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   aaa.txt
        new file:   ccc.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   aaa.txt
        modified:   ccc.txt

git checkout .を実行します。

$ git checkout .

ステージから作業ツリーにすべてのファイル(aaa.txtccc.txt)が戻りました。(ステージ未追加の変更が消された) 戻りました。

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   aaa.txt
        new file:   ccc.txt

3. HEADからインデックスと作業ツリーに特定のファイルを戻す

この場合はgit checkout HEAD -- <ファイル>コマンドを利用します。

利用例

ファイルaaa.txtbbb.txtの変更がステージに追加され、さらに作業ツリーで変更されています。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   aaa.txt
        new file:   bbb.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   aaa.txt
        modified:   bbb.txt

git checkout HEAD -- aaa.txtを実行します。

$ git checkout HEAD -- aaa.txt

HEADからステージと作業ツリーにファイルaaa.txtのみ戻りました。(aaa.txtの未コミットの変更が消された)

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   bbb.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   bbb.txt

ちなみにHEADにないファイルをチェックアウトしようとするとエラーとなります。

$ git checkout HEAD -- ddd.txt                                                                   
error: pathspec 'ddd.txt' did not match any file(s) known to git.

4. HEADからステージにすべてのファイルを戻す

この場合はgit reset HEADコマンドを利用します。

利用例

ファイルの変更がステージに追加され、さらに作業ツリーで変更されています。

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   bbb.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   bbb.txt

git reset HEADを実行します。

$ git reset HEAD

HEADからステージにすべてのファイルが戻りました。(HEADの状態でステージの内容が上書きされた。)

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   bbb.txt

no changes added to commit (use "git add" and/or "git commit -a")

5. HEADからステージと作業ツリーにすべてのファイルを戻す

この場合はgit reset --hard HEADコマンドを利用します。(HEADは省略可能)

ファイルの変更がステージに追加され、さらに作業ツリーで変更されています。

利用例
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   aaa.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   bbb.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        ccc.txt

git reset --hard HEADを実行します。

$ git reset --hard HEAD

Untracked filesを除き、HEADからステージと作業ツリーにすべてのファイルが戻りました。(HEADの状態でステージと作業ツリーの内容が上書きされた。)

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        ccc.txt

nothing added to commit but untracked files present (use "git add" to track)

過去のコミットから戻す場合

1. 過去のコミットからステージと作業ツリーに特定のファイルを戻す

この場合はgit checkout <コミット> -- <ファイル>コマンドを利用します。

利用例

HEADから1つ前のコミット391ab85からファイルaaa.txtを取り出したいとします。

$ git log --oneline
a80ef38 (HEAD -> master) なんかコミット3
391ab85 なんかコミット2
26d97d8 なんかコミット

ステージと作業ツリーはクリーンです。

$ 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

git checkout 391ab85 -- aaa.txtを実行します。

$ git checkout 391ab85 -- aaa.txt

HEADから1つ前のコミットからステージと作業ツリーにファイルaaa.txtのみが取り出せました。

$ 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 reset HEAD <file>..." to unstage)

        modified:   aaa.txt

2. HEADを過去のコミットに移動する

この場合はgit reset --soft <コミット>コマンドを利用します。

利用例

HEADから1つ前のコミット391ab85にHEAD位置を移動したいとします。

$ git log --oneline
a80ef38 (HEAD -> master) なんかコミット3
391ab85 なんかコミット2
26d97d8 なんかコミット

ステージと作業ツリーはクリーンです。

$ 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

git reset --soft 391ab85を実行します。

$ git reset --soft 391ab85

HEADが過去のコミット391ab85に移動しました。

$ git log --oneline -3
391ab85 (HEAD -> master) なんかコミット2
26d97d8 なんかコミット
e17fe0e さらに前のコミット3

ステージの内容は変更されないので、移動後のHEADとの差分がステージでは表示されています。

 $ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   aaa.txt
        new file:   ccc.txt

3. HEADを過去のコミットに移動し、ステージの内容を移動先のコミットの状態に戻す

この場合はgit reset --mixed <コミット>コマンドを利用します。(--mixedは省略可能)

利用例

HEADから1つ前のコミット391ab85にHEAD位置を移動し、ステージの内容を移動先のコミットの状態に戻したいとします。

$ git log --oneline -3
8b911dc (HEAD -> master) こみっと
391ab85 なんかコミット2
26d97d8 なんかコミット1

ステージと作業ツリーはクリーンです。

 $ 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

git reset --mixed 391ab85を実行します。

$ git reset --mixed 391ab85

HEADが過去のコミット391ab85に移動しました。

$ git log --oneline -3
391ab85 (HEAD -> master) なんかコミット2
26d97d8 なんかコミット1
e17fe0e さらに前のコミット

作業ツリーの内容は変更されないので、移動後のHEAD(=ステージ)との差分が作業ツリーでは表示されています。

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   aaa.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        ccc.txt

no changes added to commit (use "git add" and/or "git commit -a")

4. HEADを過去のコミットに移動し、ステージと作業ツリーの内容を移動先のコミットの状態に戻す

この場合はgit reset --hard <コミット>コマンドを利用します。

利用例

HEADから1つ前のコミット391ab85にHEAD位置を移動し、ステージと作業ツリーの内容を移動先のコミットの状態に戻したいとします。

$ git log --oneline -3
51e4398 (HEAD -> master) とりあえずこみっと
391ab85 なんかコミット2
26d97d8 なんかコミット

ステージと作業ツリーはクリーンです。

$ 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

git reset --hard 391ab85を実行します。

$ git reset --hard 391ab85

HEADが過去のコミット391ab85に移動しました。

$ git log --oneline -3
391ab85 (HEAD -> master) なんかコミット2
26d97d8 なんかコミット
e17fe0e その前のこみっと

移動後のHEADとステージと作業ツリーの内容が同じとなるので、ステージと作業ツリーはクリーンとなります。

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

まとめ

  • HEADまたはインデックスから戻す場合
    1. ステージから作業ツリーに特定のファイルを戻す:git checkout -- <ファイル>
    2. ステージから作業ツリーにすべてのファイルを戻す:git checkout .
    3. HEADからインデックスと作業ツリーに特定のファイルを戻す:git checkout HEAD -- <ファイル>
    4. HEADからステージにすべてのファイルを戻す:git reset HEAD
    5. HEADからステージと作業ツリーにすべてのファイルを戻す:git reset --hard HEAD

  • 過去のコミットから戻したい場合
    1. 過去のコミットからステージと作業ツリーに特定のファイルを戻す:git checkout <コミット>
    2. HEADを過去のコミットに移動する:git reset --soft <コミット>
    3. HEADを過去のコミットに移動し、ステージの内容を移動先のコミットの状態に戻す:git reset --mixed <コミット>
    4. HEADを過去のコミットに移動し、ステージと作業ツリーの内容を移動先のコミットの状態に戻す:git reset --hard <コミット>

git resetgit checkoutでHEADや過去のコミットから作業ツリーに内容を戻すときの変更は必ずステージを経由することを覚えておくと、上記のコマンド実行のイメージ画像は見やすいかと思います。

おわりに

以上、git resetgit checkoutをどんなときにどう使えばよいか?を整理してたという記事でした。

両コマンドの使い分けがいまいち曖昧だったので整理できて良かったです。

あと、参考に記載の書籍はGit解説本としてすごく分かりやすいのでおすすめです!

参考

以上