gitをなんとなーく使ってきて1年が経とうとして、いまだにaddとcommitがそれぞれ何をしているのか説明できなかったり、pushしてもなぜかエラーが起きてしまうなどの苦戦していたので、一度しっかりgitを学びました。
目次
リポジトリとブランチ
リポジトリ
変更履歴や更新者の情報も含めた一塊のフォルダ群、というイメージ
-
リモートリポジトリ
github上で確認できるリポジトリ -
ローカルリポジトリ
vscodeでもなんでも、自分のパソコンで見れるリポジトリ。git initやgit cloneをすることで.gitフォルダが入り、そこに変更履歴などの情報が入っています。さらにローカルリポジトリは- working tree
-
index
という二つのフォルダに分けられます。(厳密には違う気がしますがイメージです)
リモートが遠くのという意味だからどこかのサーバーに保存されているイメージ。ローカルは現地という意味だから手元のパソコン。 (以前ローカルポジトリのことをリモートリポジトリと言って恥ずかしい思いをしたので細かく説明してます。自分が基準で考える)
working treeとindexについて
ワーキングツリーとは、手元で作業しているファイル群、ディレクトリのことです。リポジトリとは状態の履歴なども含めていましたが、ワーキングツリーとはまさにvscodeなどのエディタで見ているファイルたちのことです。
indexは
git add <ファイル名>
をしたファイルが入っているフォルダです。この動作のことをステージングといいます。下の方々の記事がとても分かりやすいです。
git status
statusコマンドでは、赤い表示が最新のcommit状態との差分があるファイルを示します。
git diff
indexは清書用のファイル群、もしくはリモートに出荷する前のファイル群のようなイメージです。
ローカルで変更したものをいきなりリモートに反映させればいいじゃないかと考えるのも自然です。そこでローカルリポジトリがワーキングツリーとindexに分かれているメリットを考えました。
- 一度に作業しても,ファイルごとにaddをしてcommitすれば変更履歴を詳細に書ける
- commitだけだと一度更新したら更新内容を小分けにできない
-
git status
で状況を細かく把握できる- コミットの手前の段階まで把握できます。
-
git restore
が使える(多分ほぼ使わない)
つまり、出荷する際に荷物にラベル(=コミットのメッセージ)を貼ることで、荷物を受け取る人も荷物の内容がわかるのです。
この記事を書く前まで自分はほとんどgit add .
ばかり使っていたのですが、これでは「私の荷物10ファイルです。中身の確認お願いします!」と言っているようなものです。しっかりと「これは果物、これはお皿、これは電化製品」というように小分けにしていく親切さを実現するためにステージングがあると理解しています。
つまり、少しできたらcommitをする、という人にとってはステージングは不要なのかもしれません。一方で一度に作業をしたうえで最後にまとめてgit add .
をする人はaddをする前に一度立ち止まって、addするファイルを整理して複数commitにすると良いかもしれません。
ブランチ(簡単:一連の作業の流れ / 正確:コミットオブジェクトへのポインタ)
ブランチとは、作業を複数に分岐できるものくらいのイメージでしたが、実際はコミットオブジェクトのポインタであるという理解をしてgit logの見方などがクリアになりました.コミットをする際にコミットの情報が記載されたオブジェクト(情報群のイメージ)が.gitの中に生成されます。その情報群をさすのがブランチなのです。下の方の記事でとても詳しく解説しています
ポインタと聞いてぞくっとする方はc言語で躓いた経験があるかもしれません。僕は下の方の記事で理解度が上がりましたので参考までに。
-
ローカルブランチ(mainやfeature/hoge)
ローカルリポジトリに存在するローカルリポジトリの作業の分岐 -
リモートブランチ(origin)
リモートリポジトリに存在するローカルリポジトリの作業の分岐 -
リモート追跡ブランチ(origin/hoge)
ローカルリポジトリに存在するリモートリポジトリの作業の分岐
ブランチはなぜか三つあることが意味も分からずgitを使い続けていましたが,そうするとfetchやmerge,origin/mainなどが理解しにくくなります。
リモートのリポジトリは複数人が作業しているので時々刻々と変化していきます。googleドライブのように変化がすぐに手元に反映されるのならば、ローカルとリモートのブランチの二つでいいのですが、gitの場合は違います。
gitではリモート追跡ブランチという第三のブランチを手元に置き、自分の適切なタイミングでリモートの最新情報をローカルに持ってくるイメージです。そうすれば逐一オンライン環境で最新のデータを引き抜き続ける必要もないのです。イメージとしてはリモート追跡ブランチはローカルにあるのです。
リポジトリとブランチの作り方
ローカルリポジトリの作り方
git init
.gitフォルダができて,git statusなどgitコマンドが打てるようになる
リモートリポジトリの作り方
自分はgithubで作ってます。gitbucketというものやコマンドで作る方法もあるらしいです。
ローカルブランチの作り方
initで始めた場合はcommitをした段階でデフォルトの名前(git ver2.28.0以降ならmain configで変更できるらしいです)のブランチができます。なぜコミットしてからなのかというと,ブランチとはコミットオブジェクトへのポインタであるからです。先ほども紹介した「HEADとは何者なのか」が本当にわかりやすいです。
それ以外の場合には
git checkout -b <新しく作るブランチ名>
git checkout <すでに存在するブランチ名>
git switch <ブランチ名> #最近はこれが推奨されているらしいです
リモートブランチとリモート追跡ブランチの作り方(?)
これらは自ら作るというよりはpullやpushをする中でできていくイメージです。
ブランチの確認方法
ローカルブランチの確認方法
git branch
リモート追跡ブランチの確認方法
git branch -r #リモート追跡の「*r*emote」のr
リモートブランチの確認方法
githubならmainと書かれている分岐マークのあるところをクリック。
その他のコード
git branch -a
:ローカルとリモート追跡を表示
git branch -d
:ローカルの削除
git push origin --delete <branch名>
:リモートの削除)
リポジトリとブランチの同期
gitに登場するキャラをまとめたところですが、これらの接続方法について考えていきたくなるでしょう。
リモートリポジトリとローカルリポジトリの接続(remote)
git remote add <ローカルリポジトリでのリモートリポジトリの呼び名> <リモートリポジトリのurl>
addの次に長ったらしく書いているのは,いわゆるoriginです。リモートリポジトリを手元で扱いたいときに毎回urlやリポジトリ名で呼んでいたら長ったらしいので、originと命名しちゃっているんです。複数のリモートリポジトリを扱いたいときにはorigin以外の別の名前でリモートリポジトリを登録することもできるらしいです。(gitはまだまだ初心者なので、どのようなときに必要になってくるのか、実践で使ったことはないです...一応こんな記事はありました)
ローカルブランチをリモートブランチに接続(push)
git push origin <ローカルブランチ名>
これは接続というより、ローカルからリモートに押し付けるイメージです。リモートに存在しないブランチ名のブランチをpushすると、リモートに新しくブランチが生成されます。二回目以降はリモートと同じ名前のbranchをpushすればリモートも更新されます。
(参考:迂闊にブランチ名を変えたりしているとdetached HEADになりました。この原因については勉強中です。実際は名前で紐づけでもなさそうです)
リモートブランチを、リモート追跡ブランチやローカルブランチへの接続
clone
git clone <リモートのurl>
この場合、リモートリポジトリにあるmainブランチがローカルのmainブランチとして複製されます。git branch
とgit branch -r
を見るとわかるのですが,リモート追跡ブランチにはリモートのブランチが反映されているのですがローカルブランチにはmainのみが存在します。
参考までにgit branch -aの結果
test$ git branch -a
* main
remotes/origin/feature/test
remotes/origin/feature/test2
remotes/origin/main
remotes/origin/master
pull
git pull origin <リモートブランチ名>
基本的にmainのブランチにいるときにmainをプルして,feature/hogeのブランチにいるときはfeature/hogeをpullすると覚えましょう。このようにすればリモートの最新状態をローカルにまで一気に持ってこれます。混乱せずにとりあえず使ってみたい場合はpullの理解はこれくらいでいいのかと思います。
更に理解を深める場合や、fetchやmergeについても理解をしたい場合には次の文章を読んでください。
若干ややこしいのですが、pullするときの引数はリモートのブランチ名で、そのブランチを引き継ぐのは今いるローカルのブランチです。例えば
* feature/test
main
という状態でgit pull origin main
をするとfeature/testがリモートの状態を引き継ぎます。一方でローカルブランチは更新されません。
pushの場合の引数はローカルのブランチ名ですので、仮に上のようなcheckout、すなわちfeature/testにいる状態でgit push origin main
をするとローカルのmainブランチのコミット状況をリモートのmainブランチに飛ばします。
次にやること
ここまでリポジトリとブランチの大まかなイメージとコマンドについて説明してきました。
ここまでを読むと次のような疑問や問題がわいてくると思います。
-
ローカルである程度作業していてリモートの状態を追加したいときにpullしてもうまく結合できない(=競合)のではないか?→error: Your local changes to the following files would be overwritten by merge:というエラーが出ます
-
リモートのmainで作業を進めてコミットまでしたのにpushしようとしたら別の人がpushをして競合してしまう。困ったからpullをして最新の状態をリモートにpullしても競合が起きてしまう
pushによる競合のエラー
! [rejected] main -> main (fetch first) error: failed to push some refs to 'github.com:your-rep' hint: Updates were rejected because the remote contains work that you do hint: not have locally. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
pullによる競合のエラー
* branch main -> FETCH_HEAD hint: You have divergent branches and need to specify how to reconcile them. hint: You can do so by running one of the following commands sometime before hint: your next pull: hint: hint: git config pull.rebase false # merge (the default strategy) hint: git config pull.rebase true # rebase hint: git config pull.ff only # fast-forward only hint: hint: You can replace "git config" with "git config --global" to set a default hint: preference for all repositories. You can also pass --rebase, --no-rebase, hint: or --ff-only on the command line to override the configured default per hint: invocation. fatal: Need to specify how to reconcile divergent branches.
これらの問題についても知りたい方は以下のものを勉強していくとよいと思います。
-
git log
の使い方
コミットを時系列でみることができます -
fast-fowardとnon-fast-forward
コミットツリーが別々に伸びって言っている場合の結合方法 -
blobファイル、ツリーオブジェクト、コミットオブジェクトについて
- そのほかgitのコマンド一覧について