Gitのお勉強 第n幕
さて、今回もGitについてです。
タイトルが第n幕となっているのは、記事を書く際にベースにしようと思っていたメモが未保存の状態で焼失してしまったからです。
一時的なメモでもちゃんと保存はしようね!
というわけで参考テキストの順番でいうと少し間が飛びますが、
今回はリモートリポジトリとプッシュ/プルの話です。
☆はじめに
ここで扱う操作は、性質上、大元(以下リモート)リポジトリを用意してがっつり操作します。
複数の個別(以下ローカル)リポジトリから順々に操作を実行するため、順番や操作元のローカルを間違えると、意図しない結果が返ってくることがあります。
そうなると当然リモートの状態から作り直しになるため、以下のようなリストアの準備をしておくことを強くおすすめします。
(私はこれに気づかず状況を理解するまで苦労しました)
①作業直前のリモートリポジトリの物理完全コピーを確保する
②同じ状態を作れるスクリプトを用意する
③git bashに打鍵するコマンドをテキストに貼り付けて羅列する(コマンドリスト)
①については、リモートにするリポジトリのフォルダをそっくり丸ごとコピーしておけばいいです。
失敗したらコピーを再度複製してリモートに割り当てましょう。
②については独習Gitでは著者のページに生成用のスクリプトが配布されています。
③については、失敗してリストアした後に原状復帰する用です。
↓こんな感じに並べておけば、後は貼り付けるだけでロールフォワードできます。
画像では要所要所で現状を確認するためのコマンドを入れてます。
また、まとめて流す際には失敗した時と同じ轍を踏まないように注意。
前置きが長くなりましたが、どんどんやっていきましょう。
まずはリモート(神様)を参照先(崇拝)とするクローンを作ります。
ここで先に作るのはベアリポジトリというもので、ベアリポジトリはリモートリポジトリ内で管理しているファイルを直接持ちません。
では何を持つかというと、このクローンはリモートに対する参照情報だけを持ちます。
リモートを神様とするのであれば、ベアリポジトリは神様の使者のような立ち位置です。
神様が信仰者一人一人のお願いを聞いていると内容同士が喧嘩してしまうこともあるため、そこの整理は専属の使者に整理をしてもらい、整理が済んだものだけを受け取っているわけです。
※ベアリポジトリについては独自の解釈をしています。存在と役割についてはこちらを参考にしました。
クローンのベアリポジトリを作るには"git clone"を使用します。
git clone --bare {クローン元URL} {作成リポジトリ名}
作成するクローンをベアリポジトリにする場合は"--bare"スイッチを使用します。
クローン元URLはPC内のローカルであればフルパスorカレントからの相対パスを指定します。
GitHub等の外部ネットワークがソースとなる場合は、そのURLを指定します。
作成リポジトリ名はベアリポジトリの場合は末尾に".git"をつけるのが慣例のようです。
(システム上エラーとなるわけではない)
クローンのローカルリポジトリ(※)を作るのにも同じく"git clone"を使用します。
git clone {クローン元URL} {作成リポジトリ名}
先程のコマンドから"--bare"が消えただけです。
ただ、ベアリポジトリを使用する場合はベアリポジトリがクローン元となるため、実際にはクローン元URLはベアリポジトリのリポジトリ名となることが多いです。
(実行時のカレントパスによります)
"git clone"はクローン元のURLが存在しなかったり、作成しようとしているクローンの名前が既に存在したりするとエラーになります。
※厳密にはベアリポジトリも大元のリポジトリのローカルリポジトリですが、区別のためにあえてこう表現しています。
クローンを作成したらクローンのリポジトリ内で"git remote"を使用すれば指定されているソースを確認できます。
git remote -v show [{リモート名}]
リモート名は省略可能ですが、指定すれば紐付くリモートのブランチ情報が確認できます。
また、逆に省略時はリポジトリに紐付くリモート名が全て表示されます。
クローン名はデフォルトで"origin"が使用されていますが、これも"git remote"で変更ができます。
git remote rename {変更前の名前} {変更後の名前}
また、対象のリモートを追加する場合にも"git remote"を使用します。
git remote add [追加するリモート名] [リモートのソースURL]
リポジトリが参照するリモートのリファレンス一覧を見たい場合は"git ls-remote"を使用します。
git ls-remote ([対象のリモート名])
一通り確認することが終わったら、リポジトリ内でファイルを編集したり作業をしてみましょう。
コミットまでは各リポジトリ内だけで完結できるので、作業が終わったらリポジトリ内にコミットをします。
コミットが終わったら、いよいよリモートにプッシュをします。
最初の例でいえば、神様に奉納をするわけです。
プッシュは下記のコマンドで実行します。
git push ([リモートのブランチ名] [ローカルのブランチ名])
ここで、カレントのブランチ名とリモートのブランチ名が同じ場合は引数を省略して"git push"だけで実行できます。
カレントと違うブランチ名にプッシュする時は指定してあげないといけません。
また、プッシュしようとしているブランチに自分が持つコミットよりも新しいコミットが存在する場合、ローカルブランチに更新がなくても(プッシュする内容がなくても)"git push"はエラーになります。
この場合、ローカルブランチとリモートブランチで一度同期をとる必要があります。
リモートと同期をとるには"git fetch"と"git merge FETCH HEAD"を使用します。
"git fetch"を使用すると、リモートの最新コミットを"リモート追跡ブランチ"としてカレントブランチの最新コミット"に取得します。
これは、ローカル側から見れば1つの新たな分岐を持つブランチということになります。
またこのブランチのHEADは、マージするまで"FETCH HEAD"という名称で管理されます。
("git merge"の引数に出てくるのはこのためです)
もしローカルブランチに更新そのものがなかった場合、リモートに合わせて"早送りマージ"が実行されます。
これは"git merge"の挙動の1つで、更新がないのでそこは飛ばして最新コミットの整合だけを合わせる、最新のコミット時間を先に進めるだけの処理をしているので早送りと呼ばれているようです。(公式用語)
ローカルブランチに更新がなければ無事にローカルへ早送りマージされますが、もし更新に競合があった場合はそうもいきません。
その場合は自動または手動でマージを実行する必要があります。
Gitのアルゴリズムで変更箇所がないと判断された場合、"git merge"は自動で内容のマージを行い、マージしたことをローカルブランチにコミットします。
(もちろん、コミット後にはリモートへのプッシュが必要です)
同一行に別の内容で更新があった時など、Gitが自動でマージできなかった場合には手動によるマージが必要です。
手動でマージした後、自動でマージした時と同様にコミットとプッシュを行いましょう。
もちろん、プッシュの内容によっては別のリポジトリでは同様にマージ時に修正が必要になることもあります。
リモートに余分なブランチをプッシュしてしまった場合は、削除したことをプッシュすることでリモートブランチを削除できます。
そのためにはローカル上で削除したという情報を作らないといけないため、まずは先にローカルでブランチを削除します。
git branch -d {ブランチ名}
ローカルからブランチを削除したら、次にリモートにもその情報をプッシュしましょう。
git push {リモート名} :{ブランチ名}
ここでブランチ名の前に付いているコロンが重要で、"git push"の最後の引数の入力形式となっています。
入力形式のフォーマットとしては"{ソース(src)}:{反映先(dest)}"となっており、ここではdestのみ指定することで、ブランチの削除(ソースなし)という情報をリモートにプッシュしています。
タグの場合もブランチの時とフォーマットはほぼ一緒ですが、操作の順番が異なります。
リモートにタグを作成する場合はまずローカル側でタグを作成し、それをプッシュします。(タグ名を決めないとプッシュできない)
git tag -a {タグ名} -m {コメント} {タグ作成位置}
git push {リモート名} {タグ名}
リモートからタグを削除する場合は、コマンドはブランチと同じフォーマットですが、リモート⇒ローカルの順番で削除を実行します。
git push {リモート名} :{タグ名}
git tag -d {タグ名}
長くなりましたが、最後がプル(フェッチ)です。
プルは英語そのままプッシュと逆の操作で、リモートの情報をローカルに取り込みます。
ただし、プッシュの数倍は面倒臭いです。
というよりも、実際の操作的にもプッシュでこける原因がプルだったりするので、プッシュの面倒臭い原因のほとんどがプルが持ってるようなものだと思います。
何が面倒かという話に入る前に、まずプル自体について。
プルは先程出てきたフェッチとマージをセットにしたようなコマンドです。
フェッチは最新コミットに勝手にブランチを作ってくれるだけなので、問題はマージです。
マージは早送りだったりGitが自動でやってくれたりすると楽ですが、手動だといちいち確認して編集してあげないといけません。
Git自体が共同作業用の支援ツールなので仕方がないことですが、やっぱり競合のマージは神経も使って面倒なものです。
だから結構苦労するんですよね。
そしてプルがプッシュの原因となる理由ですが、プッシュしようとしたときに(コミット履歴に競合が起きていて)プッシュができないということが多いからです。
履歴の整合性を保つために一度ローカルを同期してからということになりますが、結局競合が発生する可能性はそれなりに高いと思います。
なのでプルには頭を悩ませることが度々あるようです。
ちなみにプルは"git pull"で実行できますが、これの実行はあまりおすすめされていないようです。
というのも、そもそも"git pull"自体が"git fetch"⇒"git merge FETCH HEAD"のセットコマンドで、"git merge"で想定しないマージを行ってしまうことがあるからということです。
この問題を解決するために、"git fetch"の後に"git diff HEAD..FETCH HEAD"を実行して差分を確認するという方法が推奨されており、特に初心者ほど不用意に"git pull"を使用しない方が良いらしいです。
まあ確かに実際に差分を取り込むなら中身は確認した方がいいですよね。
というわけでかなり長くなってしまいましたが、ローカルからリモートへ操作をするGit処理のお話でした。
☆余談☆
今回はリモートリポジトリを使用するということで複数の物理ディレクトリに対して(間接的に)変更をかけているわけですが、これらのリポジトリを1つの親フォルダで管理しておくと、git bashを実行した時にブラウザでフォルダを開いておけば更新時間からフォルダを操作しているのが見れて、ちゃんとリポジトリがリンクされてるってことが確認できます。