Git を使っていると git checkout というコマンドを覚える場面が何度も出てきます。ブランチを切り替えるときも、編集を間違えたファイルを元に戻すときも git checkout でした。この「ひとつのコマンドがまったく別の仕事を兼ねている」状態は分かりにくく、Git 2.23 から git switch と git restore という2つのコマンドに整理されています。この記事では、それぞれの使い方と git checkout との対応、そして「切り替えられない」と言われたときの対処までを解説します。
目次
git checkout が2つに分かれた理由
git checkout には、大きく分けて2種類の働きがありました。ひとつは「作業するブランチを移動する」こと、もうひとつは「ファイルの内容を特定の時点の状態に戻す」ことです。この2つは目的も結果もまったく違います。前者は移動するだけなので基本的にやり直しがききますが、後者は手元の編集内容を上書きして消してしまいます。
しかも、どちらの動作になるかは引数の書き方で決まります。git checkout main はブランチの移動ですが、git checkout -- index.html はファイルを戻す操作です。main という名前のブランチとファイルが両方あったらどうなるのか、と迷った経験がある方もいるのではないでしょうか。
そこで Git 2.23(2019年8月リリース)で、ブランチの切り替え担当の git switch と、ファイルを元に戻す担当の git restore が追加されました。git checkout は今も従来どおり使えるので、既存の手順書やチームのルールをすぐに書き換える必要はありません。ただし、これから覚えるなら役割がはっきりしている新しい2つのほうが間違いが起きにくいです。
なお、公式ドキュメントではこの2つのコマンドは現在も「実験的(EXPERIMENTAL)」と位置づけられており、将来オプションの挙動が変わる可能性があると書かれています。とはいえ、ここで紹介する基本的な使い方は長く安定して動いているものです。
git switch でブランチを切り替える
もっとも基本の形は、切り替えたいブランチ名をそのまま渡すだけです。
# main ブランチに切り替える
git switch main
実行すると、次のような出力が返ってきます。
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
git switch はブランチ以外を受け付けません。存在しないブランチ名を渡せばエラーになりますし、ファイル名を渡しても「そんなブランチはない」と言われて終わりです。この「うっかりファイルを上書きしてしまう余地がない」ところが git checkout との一番の違いです。
新しいブランチを作って切り替える(-c)
作成と移動を同時に行うときは -c(create の頭文字)を付けます。git checkout -b にあたるオプションです。
# 今いるブランチを起点に feature/login を作って移動する
git switch -c feature/login
# 起点を指定することもできる(main から分岐させる)
git switch -c feature/login main
起点を省略すると、現在いるブランチの先頭(HEAD)から枝分かれします。「main から分けたかったのに、作業中のブランチから分けてしまった」という取り違えが起きやすいところなので、慣れないうちは第2引数で起点を明示しておくと安心です。
直前のブランチに戻る(-)
ハイフン1文字を渡すと、ひとつ前にいたブランチへ戻れます。レビュー対応などで2つのブランチを行き来するときに便利です。
# 直前にいたブランチに戻る
git switch -
リモートにあるブランチで作業を始める
同僚が作ったブランチを手元に持ってくる場面もよくあります。git fetch でリモートの情報を取得したあと、同じ名前のローカルブランチがまだ無ければ、リモート追跡ブランチの名前を推測して自動で作成してくれます。
git fetch origin
# origin/feature/signup があれば、同名のローカルブランチを作って移動する
git switch feature/signup
# 明示的に書くならこう(別名を付けたいときにも使える)
git switch -c feature/signup origin/feature/signup
自動で作られたブランチには追跡設定が付くので、そのあとは引数なしの git pull や git push が使えます。複数のリモートに同じ名前のブランチがあると候補を決められずエラーになるため、そのときは下の例のように起点を明示してください。
コミットそのものを見に行く(–detach)
「あのバグはいつ入ったのか」を調べるために、過去のコミットの状態を再現したいことがあります。ブランチではなくコミットを指定する場合は --detach を付けます。
# 指定したコミットの状態にする(どのブランチにも属さない状態になる)
git switch --detach 9fceb02
# 調べ終わったら元のブランチに戻る
git switch -
この状態は「detached HEAD(切り離された HEAD)」と呼ばれ、ここで作ったコミットはどのブランチにも記録されません。git checkout 9fceb02 ではオプション無しでこの状態に入ってしまいましたが、git switch では --detach を書かない限り入らないようになっています。意図せず迷子になる事故を防ぐための仕様です。
git restore でファイルを元に戻す
git restore はファイル専用のコマンドです。ブランチは一切動かさず、指定したファイルの中身だけを書き換えます。
編集した内容を捨てて元に戻す
ファイル名を渡すだけの形は、作業ツリー(いま手元で編集しているファイル)をステージの内容で上書きします。まだ git add していなければ、結果として最後のコミットの状態に戻ります。
# 1つのファイルの編集を取り消す
git restore style.css
# カレントディレクトリ以下をまとめて戻す
git restore .
ちなみに git status の出力にも、この使い方が案内として表示されます。迷ったら git status を打つのが早道です。
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: style.css
git add を取り消す(–staged)
--staged を付けると、書き換える対象がステージ(インデックス)になります。ステージの内容を HEAD、つまり最後のコミットの状態に戻すので、git add したことだけを取り消せます。長らく git reset HEAD <file> と書いていた操作の置き換えです。
# 間違えてステージしたファイルを外す(編集内容はそのまま残る)
git restore --staged config.json
ここで大事なのは、作業ツリーには触らないという点です。編集した内容は手元に残ったまま、「コミット対象から外れる」だけなので、安心して実行できます。
ステージと編集内容を両方まとめて戻す
「ステージもしたし編集もしたけれど、このファイルはまるごと無かったことにしたい」というときは、--staged と --worktree(-W)を同時に指定します。
# ステージも編集内容も HEAD の状態に戻す
git restore --staged --worktree config.json
オプションを何も付けないときの対象は作業ツリーだけ、--staged だけを付けたときの対象はステージだけ、両方書けば両方が対象、と覚えておくと整理しやすいです。
過去のコミットから取り出す(–source)
戻す先は最後のコミットとは限りません。--source(短縮形は -s)に コミットやブランチを指定すると、そこからファイルを取り出せます。
# ひとつ前のコミットの状態の style.css を手元に取り出す
git restore --source HEAD~1 style.css
# 別のブランチにあるファイルを持ってくる
git restore -s main src/utils.js
この操作はファイルを手元に持ってくるだけで、ブランチの移動は起きません。「main にある最新版のあのファイルだけが欲しい」といった場面で重宝します。
git checkout との対応表
これまで使ってきた書き方が、新しいコマンドでどう表現されるかをまとめます。既存の資料を読み替えるときの目安にしてください。
| これまでの書き方 | 新しい書き方 | 意味 |
|---|---|---|
git checkout <branch> | git switch <branch> | 既存のブランチに切り替える |
git checkout -b <branch> | git switch -c <branch> | ブランチを作って切り替える |
git checkout -B <branch> | git switch -C <branch> | 同名があっても作り直して切り替える |
git checkout - | git switch - | 直前のブランチに戻る |
git checkout <commit> | git switch --detach <commit> | コミットを直接参照する状態にする |
git checkout -- <file> | git restore <file> | 編集内容を捨てて戻す |
git checkout <commit> -- <file> | git restore -s <commit> <file> | 指定したコミットからファイルを取り出す |
git reset HEAD <file> | git restore --staged <file> | ステージから外す |
restore は取り消せないので実行前に確認する
git restore で作業ツリーを戻すと、そこにあった編集内容は完全に失われます。コミットしていない変更は Git のどこにも記録されていないため、git reflog でも取り返せません。半日かけた修正を一瞬で消してしまった、という事故はこのコマンドで起こります。
消していいか少しでも迷うなら、先に退避しておくのが確実です。git stash を使えば変更を保管したうえで作業ツリーをきれいにできますし、あとから git stash pop で戻せます。
# 何が変わるのかを先に確認する
git diff style.css
# 捨てずに退避しておく
git stash push style.css
また、git restore . のようにディレクトリごと指定すると、意図していなかったファイルまで巻き込みます。範囲を広げる前に git status で対象を確認する習慣をつけてください。なお、.gitignore で除外しているファイルや、まだ一度も git add していない新規ファイルは Git の管理外なので git restore の対象になりません。
switch できないと言われるとき
未コミットの変更があって切り替えられない
もっとも多いのが次のエラーです。
error: Your local changes to the following files would be overwritten by checkout:
style.css
Please commit your changes or stash them before you switch branches.
Aborting
切り替え先のブランチでもそのファイルの内容が違うため、そのまま移動すると手元の編集が上書きされてしまう、という警告です。編集が消えるのを防ぐために Git が処理を中断しています。対処は、区切りが良ければコミットする、まだコミットしたくなければ git stash で退避する、本当に不要なら git restore で捨てる、のいずれかです。
なお、編集中のファイルが切り替え先でも同じ内容であれば、変更を抱えたままでも問題なく移動できます。エラーが出るのは、あくまで衝突するときだけです。
switch is not a git command と表示される
次のような出力になる場合は、Git のバージョンが古く、そもそもコマンドが存在していません。
git: 'switch' is not a git command. See 'git --help'.
まずは git --version でバージョンを確かめてください。2.23 より前であれば git switch も git restore も使えないので、Git を新しくするか、従来どおり git checkout を使うことになります。サーバーにログインして作業しているときや、古い CI 環境のスクリプトを書くときに出会いやすいエラーです。
invalid reference と言われる
fatal: invalid reference: feature/login のように出るときは、指定した名前のブランチが見つかっていません。タイプミスのほか、リモートにはあるけれど手元にまだ取り込めていない、というケースが考えられます。git fetch を実行してから、git branch -a で存在する名前を確認してみてください。コミットのハッシュを指定したつもりなら、--detach を付け忘れていないかも確認します。
まとめ
git switch はブランチの切り替え専用、git restore はファイルを元に戻す専用のコマンドです。git checkout が兼ねていた2つの役割が分かれたことで、コマンドを見ただけで何が起きるか分かるようになりました。ブランチを作って移るなら git switch -c、直前のブランチへ戻るなら git switch -、コミットを直接見るなら git switch --detach です。ファイル側は、編集を捨てるなら git restore <file>、git add を取り消すなら git restore --staged <file>、過去の状態を取り出すなら git restore -s <commit> <file> と使い分けます。git restore による作業ツリーの復元だけはやり直しがきかないので、迷ったら git diff で中身を確かめるか git stash に逃がしてから実行してください。