ユータローです。昨日今日と自分用の備忘としてまとめていましたが、いい機会だったのでQiitaを初更新してみます。間違ってたらすいません。
#書いた理由
Gitを始めて触ってから一年ほど。業務に入ってからpull requestやブランチのマージという初歩的な部分でつまづくことが多かったので、一旦gitの基礎から叩き直すことにしました。
これまでの認識としては
git add ファイル #ファイルをステージ(という一時保管場所におく)
git commit -m ‘messaage’ #変更内容を記す
git push origin ブランチ #公開する
程度で、一連の作業としてとりあえずやっておけばいいようなところがあったと思います。ただ当然ながらこれではイレギュラーの事態が起こった時に対応することができず、コマンドを試しに打ってみて動くか確かめるような状態でした。
よくあるイレギュラーの事態としてはブランチを跨いだ時が多かったです。例えば、masterから切ったsquashブランチで作業していたとして、そのままvscodeでmasterに戻ってみると、なぜかsquashブランチでの変更がmasterで取り込まれてることがあったりしました。
大抵は力技で上書きして戻していましたが、どうやらgitのブランチがあくまでハッシュ番号を示すポインタ(HEAD)に過ぎず、コミットしない限りはgit側で感知するすべがないということ、またブランチを切り替える時には、working tree is cleanという状態でなければいけないとのことでした。
そこでworking tree is cleanとは何なのか、そしてHEADとは一体何を指すのかについて自分なりに学んだことを共有しようかなと思います。
#ローカルでの動き
まずローカルにおけるgitの動きについて解説します。(誤解してましたが、ターミナルで触ってるものがgitで、githubはgitをGUIで確認するためのサービスの一つにすぎないんですね笑。。。)
ローカルにはまず作業ブランチ、ステージ、リポジトリの三つの場所があります。(追跡ブランチについては後述)。
・作業ブランチは普段コーディングをする場所。
・ステージはコミットする前の一時待機場所。
・リポジトリはコミットやコミット後のコードを管理する場所。
git add ファイル名
これによって作業ツリーからステージにファイルをアップロードします。コマンド直後は作業ツリーとステージの内容は同期されています。あくまでコピーなので間違えてしまった時はgit addで上書きできます。
git commit -m ‘message’
これはステージからリポジトリにコミットをするものです。コミットのたびにハッシュという番号が割り振られ、事後的に一意にコミットを識別することが可能になります。最新のコミットのハッシュを保持したポインタがHEADです。コミットをするたびにHEADも更新され、ツリーのように繋がっていきます。
これがコミットするまでの一連の流れです。
コミット履歴を確認するためには git logを使います。
git log --oneline --graph #ブランチとか見やすいオプション
git log -1 #一つ前のコミット
git log — ファイル名(ブランチ名)
履歴を限定して確認もできます。
上記の三つの場所の差分を確認するのがgit statusです。これはリポジトリにおけるHEADが指すコミットと、ステージ、作業ツリーの差分を示すものです。
changes to be committed
の下に書かれているファイルがgit addでステージに入ったものです(つまりHEADとステージの違い)
changes not staged for commit
この下にはgit add前のステージに入ってない変更点があるファイルが表示されます。(つまりステージと作業ツリーの違い)
僕自身、Log::debugのようなログファサードをよく書き入れてしまいますが、これだとここにファイルがたくさん表示されてしまい、あまり良くない状況(だそうです。)。変更を辿っているのではなく、あくまで状況の差分を示しています。
git diff
#具体的な差分をコマンドラインで確認するためのコマンドです。
git diff #作業ツリーとステージの差分
git diff —staged #ステージとHEADの差分
git diff HEAD #HEADと作業ツリーの差分。
これを使えばコミットをしてgithub上で確認しなくても、変更(見えにくい空白行などの差分)が確認できます。
ちなみにコミット番号を指定すれば、コミットを跨いだ差分が確認できます。
git diff 2fb595 479ab3
git diff HEAD^ HEAD
^で一つ前^^で二つ前を表します。
差分ではなくコミットそのものを確認したい場合は
git show コミット番号
git show HEAD^ #一つ前のコミット
のように指定します。
ステージの状況を確認することは(恐らく)あまりなさそうですが、
git ls-files
で確認できるそうです。
#HEADの位置の調整(つまり削除)
HEADの位置の調整が削除に値するという事実が、個人的にはgitの理解が最も進んだような気がしました。
git reset
これはステージにある内容を、HEAD(コミットしてないので前回のコミット内容)と同期させます。つまりステージがリセットされるということです。合わせて作業ツリーまでHEADに合わせる(リセットする)には
git reset —hard
git reset コミット番号
をします。またgit reset コミット番号で、そのコミットまで戻ることができます。同じく—hard オプションでステージと作業ツリーまで同期できます
git rm
をすればファイルを管理下から外す(ステージから外す)ことができますが、 同時にそのブランチ上では作業ツリーからファイルも削除されてしまいます。
#ブランチ
ブランチとはローカルにおける本流(master)から切られたもう一つの作業スペースのようなもので、(エラー文を見ると)最終的には本流にマージされることが想定されてるようです。
HEADとは最新のコミット番号を示すものですが、git checkout ブランチでブランチに乗ると、HEADはそのブランチにおける最新コミットに合わせられます。そのためブランチを切り替える時には、作業ツリーとステージとHEADが一致しているクリーン**(working tree is clean)**な状態でやるのが最善だそうです。作業ツリーを中途半端に書き換えたり、ステージにコミットしてないものがあると未コミットの変更が失われてしまいます。
作業が終わったらプルリクエスト出すか、mergeします。git mergeはあくまで枝分かれしたブランチ上ではなく、本流でやります。仮に編集場所がバッティングして、git側がどちらを優先していいか分からない時にコンフリクトが発生します。コンフリクトが発生するのはファストフォワード(後述)ではない時で、手動でどちらを優先するかをgitに伝える必要があります。
コンフリクトを手動で削除し終わったら、git addをしてもう一度ステージングに追加します。git statusで同名のファイルが複数なければコンフリクトはおそらく無事解消されてます。
git merge —abort
をすることで、git mergeコマンド前の状態にロールバックをすることができます。
ブランチの削除には
git branch -d ブランチ
git branch ーD ブランチ #マージをしていない場合
をします。
マージには二通りあって、ブランチの分かれ目であるmasterに更新がない(つまりブランチのみが伸びている)場合、マージしようとするとそのブランチがそのままmasterの先にくっつきます。原則的にはコンフリクトは起こり得ません。これを早送りマージ(fast forward)と言います。
git merge —no —off
これで早送りマージをさせないようにすることができます。
ここまではローカルの動きです。ここからはリモートを含めて話をします。
#リモートレポジトリ
レポジトリにはローカルとリモートがあり、これまでのレポジトリはローカルを指してました。しかしながら他人が参照できるようにしたり、公開する場合はリモート(github)にローカルの内容を送らなければいけません。これがgit pushです。
リモートの名前としては慣習的にoriginという名前が与えられています。プッシュをする際は
git push origin(リモート名) ブランチ名
をします。これで手元のローカルとリモートの内容が同期されます。ハッシュ番号はそのままコピーされます。
ローカルの変更をgit pushでリモートに送るので、必然的にローカルの方がブランチが伸び、リモートとの若干の差分が生じます。また他人が同じブランチで開発してると、その差分を自分のところに取り込まなくてはいけません。
この差分をローカルで管理するのが追跡ブランチです。origin/ブランチ名というようにスラッシュですぐに続いてるものが該当します。内容を同期するにはgit fetchをする必要があります。
git fetch origin ブランチ名
あくまで追跡ブランチに取り込むだけなので、作業ツリーやステージ、ローカルレポジトリには影響を与えません。fetchのあとは手動でgit merge —ff—only origin/masterのようにしてマージする必要があります。
git pull origin master
をすると一括でマージまでを行えます。自分自身、非常によく混同してしまうのですがorigin masterはリモート、origin/masterは追跡ブランチを指します。
先ほど解説したgit diffは
git diff origin/master ブランチ
のように追跡ブランチとリモートの差分を見るのにも使えます。(※個人的には追跡ブランチの概念の把握がないとfetchとpullの違いもいまいち腑に落ちませんでした。)
自分用の備忘でもありますが、新しくブランチを作るときの作業は、
git fetch origin && git checkout -b feature/xxxx origin/develop
で、これはgit fetchで追跡ブランチにとりあえずリモートの内容を反映させ、checkout -bでorigin/developを元にして新しくブランチを作る挙動になります。
そしてこれで
git push origin feature/xxxxx
でリモートにプルリクエストを出します。
#最後に
自分なりにここ最近gitについて学んだことを書き出してみました。ただこうやって知識があっても、経験がないと使えるようにはならないのは自明なので、実務を通してもうちょっとgitに馴染んでいきたいと思います。体系的にまとまった知識がないと小手先でこねくり回るのが苦手な(根っからの)文系脳でもあるので、全体像を掴むという意味で、周辺の知識をこうして仕入れることも大事だったのかなと思います。
読んでいただきありがとうございました