昨今ではリポジトリ管理ツールとして、Gitが使えるのは当然になっている。
しかし、いつものお決まりのコマンドを覚えるだけに留まっていないだろうか?
何かが起きたとき、トラブルシュートできるだろうか?
誰かが困っていた時に、サポートしてあげることができるだろうか?
いつものお決まりの流れが、本当はどういう仕組みで動いているのか?や、普段使わないコマンドについて、どういう機能のものがあるのか?を理解することは、プロジェクトチーム全体がGitによる事故を0に抑えるために非常に有用である。
※本記事で説明する git checkout と git reset は、「HEADの移動またはブランチの移動」についてのみ焦点を当てて説明する。
これらのコマンドには様々なオプションがあり、本記事が対象とする以外の機能も持っている。
それらについて網羅的に知りたい場合は、Gitの公式ドキュメントがおすすめである。
git checkout と git reset に関する誤り
git checkoutはほとんどの方が使われていると思うが、git resetはどうだろうか。
実は、使う人はとてもよく使うコマンドである。
それでこの2つのコマンドがちょっと似ており、どちらも、今いるブランチとは別のブランチやコミットに関する操作をする。
そしてこの2つのコマンドに関するネット上のサイトの説明が、誤っているものが大変多いのだ。
【誤っているポイント】
- git checkoutは作業ディレクトリを破壊するが、resetは破壊しない
- 「2つのコマンドの違い」という観点で説明する点としては誤り
- コマンドの仕様上、結果的にそうなる、というだけで「違い」はもっと前段階、根本的なところにある
- git resetはコミットを取り消すコマンド、または特定のコミットに巻き戻すコマンド
- これは結果的に戻せる、というだけでコマンドの説明としては誤り
- 本質は、特定のコミットにブランチHEADを移動できるコマンド
- 戻す以外に進めることもできるし、枝分かれした全く関係ないコミットにも移動できる
- git resetはHEADを移動・変更するコマンド
- 明らかな間違いではないのだが言葉足らずというか、初学者は勘違いしてしまう説明になりそう
- 正確に言うと、ブランチHEADを移動・変更するコマンド
- よく遭遇する、ただの「HEAD」とは少し内容が異なる
上に挙げたような説明をしているサイトがゴロゴロゴロゴロ、溢れている。
しかも揃いも揃ってこれらが、検索結果の上位を占めているのだ。
検索エンジンさん・・・、頑張ってくれぇぇぇ・・・。
結論
図を使って簡潔に説明する。
図の説明や具体的な説明は後の章で、詳細に記載する。
git checkout
上図のように、HEADの中身を変更する = HEADが参照するブランチを変更する。
要するにブランチを移動すると同義であり、HEADが新しく示すブランチ(またはコミット)に、現在位置が移動する。
git reset
上図のように、ブランチの中身を変更する = ブランチが参照するコミットを変更する。
checkoutの方の言葉と対応させてみると、ブランチが移動すると言ったらいいのかもしれない。
HEADが示すブランチは変わらないが、ブランチが示すコミットが移動するので、それにつられて現在位置が移動する。
そもそもブランチとは?HEADとは?
上図のように、
- ブランチ
あるコミットを示すポインタ。
コミットツリーの各枝の先端のコミットや、枝分かれの支点となるコミットを示すことが多く、「意味のあるコミット」であることが一般的。
ブランチを参照することで、支点となるコミットから新しいコミットを生成したり、各枝の先端に新しいコミットを生やしたりすることができる。 - HEAD
あるブランチを示すポインタ。
今、自分がどこにいるのか?を把握するために最初に見るところ。
HEADが示しているブランチを見て、ブランチが示しているコミットを見ることで、現在位置を把握している。
実はブランチを経由しないで、適当なコミットを示すこともできる。(detached HEADと呼ばれる)
git checkout と git reset
ブランチとHEADの概念がはっきりしたところで、改めて見てみる。
git checkoutはHEADの中身を変えるコマンドであり、ブランチには何も影響ない。
今あなたが開発を始めようとしている位置を、移動する。それのみである。
例えば上図では、これまでdevelopAが示すコミット#4において開発していたのだが、git checkoutによるHEADの変更に伴い、developBが示すコミット#5で開発できるようになった。
一方、git resetはブランチ(ブランチHEAD)の中身を変える。
上図ではdevelopBの中身が変わったことにより、HEAD「developB」で新しくコミットした場合は、コミット#3を親としてコミットオブジェクトが生成されるようになる。
また、例えば
git reset --soft HEAD^
とすると、ブランチが示すコミットを、現在示しているコミットの一つ前のコミットに変更することができる。
これがよく「コミットを取り消す」という風に説明される所以だが、コマンドとしての本質は「取り消し」でなく「移動」であることが分かると思う。
そして勘違いされやすいポイントだが、resetする前にいたコミットは削除されるわけではない。
あくまでブランチが示す先が変更するのみなので、コミット自体は残っている。
下図のような状態である。
つまりコミット#5は生きてはいるけど、どこからも参照されうることがないコミットになる。
もし取り戻したい際には git reflog というコマンドと git reset コマンドを組み合わせて、取り戻すことができる。
またGitの公式ドキュメントには、git reset について以下のような説明がある。
set the current branch head (
https://git-scm.com/docs/git-resetHEAD
) to<commit>
現在のブランチHEADを<commit>に設定する、と書いてある。
ちょっと混乱するが、この「ブランチHEAD」は上記で説明してきた「HEAD」ではなく、「ブランチ」のことだと思ってもらえば良いかと思われる。
最後に
まとめると、
git checkoutはHEADを変更するコマンドであり、
git resetはブランチ(ブランチHEAD)を変更するコマンドである。
様々な情報が点々としている昨今、正しい情報の見極めが必要だ。
やっぱり公式ドキュメントが神、っていうのと、気になったら適当なリポジトリ立ててテストしてみるのが一番早くて理解が進むと思う。
Gitのトラブルシュートやサポートができる人材はとても重宝される。
さああなたもGitマスターになって、バリバリ開発しよう!
コメント