
Gitで詰まるところがよくあるとおもいます.少しまとめてみました。
本記事では,リモートリポジトリの名前はorigin,メインブランチ名はmainとします.
最初に,Gitにおける変更の最小単位は「コミット」です.
?となったと思うのでゆっくり解説します.
コードを書いていると,ファイルに変更が加わります.Gitはこの変更を記録して,ファイルのバージョン管理をします.
最初に白紙のファイル「a.txt」を作りました
→a.txtの1行目に「うに食べたい」と書きました
うに食べたい→a.txtの2行目に「たこ食べたい」と書きました
うに食べたい
たこ食べたい→a.txtの1行目の「うに食べたい」を「かに食べたい」に変更しました
かに食べたい
たこ食べたいこのように変更を記録していけば,バージョン管理できます.
この「変更」をgitに記録させる処理こそが「コミット」です.
git add .とよく書きますが,これは「コミット」するものを選ぶ処理です.
ちなみに,git addしたものをaddしてない状態に戻したい時はgit reset <ファイル名>でokです.
さらにちなみに,この「.(ドット)」は,現在のディレクトリを指定していることになり,現在のディレクトリ配下全てのファイルを選択することを意味します.個別で選びたいファイルがあるときは
git add hoge.txtなどで指定してください.
さらにさらにちなみに,バイトをしていた時よく言われたのは「コミットは小さく」という話でした.コミットが最小単位であるため,同じコミットに関係ない変更をたくさんいれてしまうと良くないことが起こります.
例えば,LEDのコードの変更と,足回りのコードの変更を同じコミットに入れたとしましょう.
もし,後に足回りのコードがミスってた場合,足回りのコードだけを戻し,LEDをそのままにするということをするには複雑な手順がいります.また,GitHubでPull Requestを出す時,同じコミットで出された関連性のない変更でレビューアが困惑することもあります.
コミットは小さい方が何かと便利なので,git addで,LEDのコードだけaddし,commit,そのあと足回りのコードだけaddし,commitとやるのがおすすめです.
コミットできたら,そのコミットはバージョン管理の最小単位として振る舞います.
例えば,下記のようなブランチツリーの節点はコミットです.
A -> B -> CA,B,Cはそれぞれコミットだとします.現在Cまで変更を重ねてきたけど,バージョンを巻き戻したい!となったら,Bまで戻る!とか指示することになります.
ここからブランチについて話します.ブランチを初めて学ばれた方は困惑すると思います.
ブランチとは,コミットの別名です.
枝分かれした枝のこと,と解釈することは多いですが,実際はそう考えるよりも,コミットの別名と考えたほうがわかりやすいです.
A -> B -> C -> D(main)A,B,C,Dはコミットです.Aからコミットを重ねてDまで到達して,今のmainブランチがあります.ゆえに,mainブランチはDの別名です.
gitにおけるコミットは,レイヤーのようなもので,最初の状態から,A, B, C, Dが独立に変更を保持しています.
Aがa.txt作成,Bがb.txt作成,Cがc.txt作成,Dがd.txt作成というツリーなら,各自が空のディレクトリに独立にtxtを作った結果がmainです.
したがって,もしBというコミットを引っこ抜いて,A -> C -> Dというツリーを作ったら,ディレクトリにはa.txt, c.txt, d.txtがあります.
ここで,ブランチを作成しましょう.
A -> B -> C -> D(main)
└─> E(sub)もちろん,Eもコミットです.
Dという状態から,ブランチを作成し,新しくEというコミットを作るとこのツリーになります.
ブランチ作成コマンドは2通りくらいあります.
git checkout -b sub
#または
git switch -c sub後者のほうがナウいです
mainブランチで,これを実行すれば上記のツリーになります.
なぜDからEに分岐しているかと言うと,それはmainがDのエイリアスであるからです.
ブランチがコミットのエイリアスであることを確かめたいと思います.
まず,上のツリーを把握するために下のコマンドを実行します.
git log --onelineこれは,これまでのコミットを一覧表示してくれます.
31392d2 (HEAD -> sub) E
61d94a5 (main) D
ad7ffdc C
92ab5a1 B
35c5b17 A※先頭のランダムっぽい文字列は環境によって異なります.
mainがDに紐づいています.また,subがEに紐づいています.
ここで以下のコマンドを実行します.
git switch -c hoge 92ab5a1※92...の部分はBの横のやつにしていい感じに合わせてください.
すると,hogeブランチに移動します.ここで,git log --onelineと実行しましょう.
92ab5a1 (HEAD->hoge)B
35c5b17 Aつまりタイムスリップしてる~~~!?ってことです.
となり,フォルダは
$ ls
a.txt b.txtみたいになると思います.
これはつまり「現在のバージョンをBのコミット時点まで戻した」と同時に「Bのコミット時点に別名hogeを付けた」ということになります.
もとに戻りたいときは,
git switch mainで戻れます.
まとめると,ブランチはコミットの別名です.コミットの別名を使って,あちこちの「状態」に飛べるということになります.
マージは,先程のコミットの話でなんとなくわかってくると思います.
A -> B -> C ->D (main)
└─>F(sub)というようなツリーだった時,mainの状態にsubの変更を上乗せしたい!となったら,mainブランチで,
git merge subで以下のようにうまくいきます.
A -> B -> C ->D -> G(main)
└─>F(sub)───────┘基本,マージしたらマージコミットと呼ばれる新しいコミットが生成され,マージコミットGにmainというエイリアスがつきます.
今の「基本」の例外もあります.有効化していると以下のようなツリーの場合ファスト・フォワード(Fast-Forward)というマージがされます.
それは以下のような場合です.
A (main)
└─>F(sub)この場合,
A
└─>F(sub)(main)ここで,現在の状態はFになります.
※sub,mainが同じコミットを指しているという状態になります.この状態はよくあることですが,branchの解釈が「コミットのエイリアス」でない場合,困惑するポイントです.
マージは,後述するrebaseと同じように,複数のブランチを束ねるコマンドです.
ときに,
A -> B(main)
└─>F(sub)BとFで同じファイルをいじり,
git merge subをmainブランチで実行することもあると思います.
このときは「conflict」が発生します.お互いに同じファイルを書き換えちゃって,それらが競合しているということです.
この場合,gitは両方の変更をわかりやすく表示しながら,同じファイルに書き込み,無理やりがっちゃんこします.大抵の場合,無理やり書かれたところを人間が修正することになります.(VSCodeは専用の機能があります)
複数ブランチのまとめ方には代表的なものとしてもう1つあり,それはrebaseです.
git rebase main subのように使います.
例えば,
A -> B -> C ->D -> G(main)
└─>F(sub)というツリーがあったとします.
subブランチで上のgit rebase ...を実行すると,
A -> B -> C -> D -> G (main) -> F'(sub)となります.
ここで,F'は何かというと,変更はFと同じ,ただし,git log --onelineで表示される左端の英数字が変わったコミットというものです.ちなみにこの英数字のことをハッシュといいます.
rebaseの効能は「分岐元を変更する」というものです.こういうとなんか魔法モノのアニメみたいですね.もともと,Aから生えていたFでしたが,その分岐元をmainに付け替えます.ハッシュは,辿ってきたコミットの流れから算出されるため,分岐を付け替えたことで,ハッシュは変更せざるを得ないため,同じ変更をもつ別コミットとして扱われます.
チーム開発したら絶対に起こりますよね……
例えば,自分がGitHub(リモート)に上げたコードを誰かが編集して,GitHub(リモート)にpushしたときとか,1人でやってても,GitHubの画面でREADME.mdを書き換えたときなど.
GitHubのリポジトリから,Codeを押して,zipでダウンロードし,今のコードと差し替える
→面倒です
git fetch origin main
git merge origin/maingit pull origin main2つ書き方があります.ちょっと長い話になります.
gitは,コードの分散管理が設計思想としてあります.
したがって,PCにあるデータとGitHub上にあるデータは自動同期されていません.これはご存知だと思います.したがって,PCには,PC上にあるリポジトリのブランチ名が保存されています.
ただし,実はこれに加えて,リモートリポジトリのブランチを追跡するブランチもPCに保存されています.そのブランチ名は「origin/main」です.(mainはリモートリポジトリのブランチの名前と一緒)
git fetchコマンドは,リモートリポジトリのブランチを追跡するブランチの情報をインターネット経由でリモートから取ってきて,同期します.
このあと実行しているgit mergeは,複数のブランチをがっちゃんこするものですが,このコマンドをmainブランチで実行すると,mainとorigin/mainをがっちゃんこします.つまり,今あるPC上のmainブランチにorigin/mainの変更を上乗せしているのです.これはまさしく,GitHub上にあるデータをPCに同期する処理ですね.
下のコマンドgit pullは,pushと対照的で覚えやすく,よく使われているコマンドですが,このコマンドは上のコマンドを一括で実行するショートカットみたいなコマンドです.
基本はgit pullが早くていいのですが,GitHub上の変更のうち,PC上に適用したくないものがあったときの融通が利かない欠点もある(前者の方法ではこの辺の融通が利きます)ので,上記の話はなんとなく知っておくと便利だと思います.
制御班