概念理解してコマンドを使い、コンフリクト等を防ぐ
順序の話なんですが、gitをしばらく使っててコンフリクトしたから概念調べてようやく理解した感じです。コンフリクトの他にも古いサーバーの古いシステムは、本番でも本番用にいじる必要があったりで、素直にpullできない状況が少なからずあります。
大事なのは、起こる原因をgitの仕組みから理解して対処すること、コンフリクトはどちらを正とするかなのでそのコードの読解の方が重要になる、ということを学びました。
とりあえず、これからは気をつけられそうです。今回は前回の記事より少し深掘りした話になります。図を使って説明するより文章多めの言葉で纏めています。筆者、言語化できないと不安マンなので、ご容赦ください。
ちなみにコマンドの持ちうる機能面の方の説明を多めにした記事はこちらに纏めてあります。
機能だけで覚える基本的なGitコマンド 〜ほぼほぼ作業順〜
#概念とコマンド
gitの本質
ディレクトリやファイルの差分管理。決してコピーするのではなく、差分をハッシュ(コミットID)やブランチを使って管理する。
リポジトリとブランチ
リポジトリ
開発しているソースコードなどが格納される貯蔵庫です。リポジトリの名前は、デフォルトでoriginとなっています。githubでリポジトリを作成する時にリポジトリネームを決められますが、あれとは紐づいていないんですね。
###ブランチ
リポジトリの中でソースコードに対してポインタを設置し、変更点だけを差分にしてファイルを作り変えてくれます。これでブランチで分けたところはお互いに干渉しないように分離されたような状態になります。要するに、ブランチとはリポジトリという貯蔵庫の中に壁を作り、壁によって分けられているのは変更があった差分となっている状態ということです。
これが出張るのは、抽象的に言えば、リポジトリの中で分離させたい要素が生まれた時、具体的には、追加機能があった、複数人で開発をしている、既にmasterブランチが本番環境で動いているなどが挙げられます。
###リポジトリのローカルとリモート
リポジトリにリモートとローカルがあります。これは、自分の作業する環境をローカルリポジトリとして、pushするgithubをリモートリポジトリとして見ます。ローカルを自分の作業環境と表現したのは、作業環境が自身のPCかもしれませんし、デプロイサーバーかもしれないからです。筆者はローカルというとすぐ自分のPCを想起してしまいがちです。自分のいる場所、それがローカル。
###ブランチのローカルとリモート
リポジトリにローカルとリモートの概念があるなら、当然のようにブランチにもあります。ローカルブランチ(origin)とリモートブランチはコマンドを使用することでおおよそ仕組みが理解できます。ローカルブランチがoriginとなり、ローカルブランチからpushされるとこのリモートブランチが更新されます。
$ git checkout -b branch_name
上記のコマンドを使用すると、ローカルのリポジトリに、ローカルブランチとしての壁が作られます。この時、リモートリポジトリにはまだリモートブランチはできていません。ローカルリポジトリからリモートリポジトリへpushされると、その段階でリモートリポジトリにリモートブランチが作られます。
$ git checkout branch_name
上記のコマンドでブランチを切り替えることができます。
####ローカルブランチとリモートブランチの削除
閑話休題ということでブランチの削除についてここに書きます。
ブランチが削除されるタイミングは、masterブランチへmergeがなされた時です。web上のgithubでプルリクエストをmergeする時にブランチをクローズするチェックボックスがありますが、これやってもリモートブランチしか消えません。ローカルのブランチは残ってます。mergeさせてmasterをスッキリさせたら役目の終えたブランチもローカルとリモートの双方向から削除して完全スッキリさせるといいと思います。
$ git push --delete origin branch_name・・・リモートブランチの削除
$ git branch --delete branch_name・・・・・・・・・ローカルブランチの削除
##リモートリポジトリの中のリモートブランチにpush
リポジトリとブランチがわかっていると、この見出しにも納得が行くと思いますし、勘のいい人であるなら既にpushコマンドの記述もどうやってpushされるのかわかると思います。
pushする手順は、add=>commit=>pushの三段階を踏むと思います。
この三段階を踏むのは、なぜかというとgit initする時点でローカルに領域として四つほど作られます。三段階なのになんで四つやねんと言いたいところですが、このうちの一領域は別コマンドを使った時に使用されます。
これに関してはstashコマンドを使うタイミングで利用されます。
stashコマンドに関してはこちらです。
gitのpush周りで問題が起きた時の対処
ひとまず、pushまでの過程で使われる領域は、このうちの三つです。
一つは、ワーキングスペース。ソースコードをいじる場所です。
二つ目が、ステージング。これでaddされた時にファイルが移動する場所です。
三つ目が、ローカルリポジトリ。これがcommitされた時にファイルが移動する場所です。
この後にpushすることで初めてリモートリポジトリに移動します。
$ git init・・・・・・・・・・・・・・・・・・・・・・・ワーキングスペース・ステージング・ローカルリポジトリ・
$ git add file_name・・・・・・・・・・・・ステージンング領域に移動
$ git commit -m "message"・・・・・ローカルリポジトリに移動
$ git push origin master・・・・・・リモートリポジトリに移動
addはステージングにあげることで、ディレクトリやファイルを切り分けられたり、作業途中のファイルを除いてリモートリポジトリに入れる準備ができる役割を担っています。
commitはメッセージをつけて機能の説明や意図をコードに添えて送れます。
pushでは、リモートリポジトリのあるリモートブランチに対してコミットされた、つまり、ローカルリポジトリに上がったファイル送る命令を書いていますが初めてgitに触った時はこのoriginって何ぞ?と思っていましたが、先述のリポジトリの概念を理解してようやく腑に落ちた感じです。このoriginはリモートリポジトリを指していて、そのリモートリポジトリのmasterと言う名前のリモートブランチに向けてpushすると言う意味になっています。
#git pullとgit fetch・git mergeセット
git pullした時に不思議だなーと思ったのは、開発者の一人が、mergeしたからpullしてーって言った時にpullしたら自分の作業内容消えんじゃね?と思って躊躇してたら周りは一斉にpullを始めたので、自分もおっかなびっくりpullしたら自分の作業内容は消えずにその開発者がmergeしたところだけが反映されたことです。pullは、リモートリポジトリのファイルをコピーしてきてローカルのファイルへ上書きするのかと勝手なイメージを膨らませていましたが、gitは、差分を管理するという概念に則り、差分のあったファイルだけ作り変えてくれているようです。
pullの仕組みを知るためには、git fetchとgit mergeの仕組みを理解する必要があります。
なぜなら、pullはこの二つのコマンドを一気にやってくれちゃうものだからです。
ただその前に、ここでまた一つ、理解すべき概念があります。
#### リモート追跡ブランチと上流ブランチ
なんやねん、このブランチって思うかもしれませんが、これが重要な役割を担います。コマンド上では、 origin/master と言うように表記されます。
まず、リモート追跡ブランチは、ローカルブランチの上位存在で、ローカルリポジトリ内にあります。え?リモートって名前についてるのにローカルに?と思うかもしれませんが、これ言葉足らずというか日本語の修飾の仕方に多様性がある弊害の一つのように思われます。ここについてはまた後述します。
リモート追跡ブランチと上流ブランチの違いは、pullコマンドとfetch・mergeコマンドの仕組みを理解する必要があります。リモートリポジトリから落ちてくるファイルは、いきなりローカルのリポジトリにストンと落ちてきてくれていないのです。まず、このリモート追跡ブランチに落ちてきます。実際は、このリモート追跡ブランチから、ローカルのリポジトリに落としています。この二段手順がそれぞれfetchとmergeです。
**リモートからリモート追跡ブランチに落とすのが、fetch。リモート追跡ブランチからローカルブランチに落とすのがmergeです。**このように上から落として、落とすイメージがあれば上流ブランチのイメージはすぐ持てると思います。そう、上流ブランチは、git pullした時に引き数を指定する必要もなく差分を落としてくることができるブランチを指します。
次に先ほどあったリモートって言葉を冠してるくせにローカルにあんのかよってことなんですが、自分のようにこんなこと考えない人もいると思うんですが、自分はこのリモートって言葉がブランチにかかってると思ったんですね。そうでなくて、このリモートは、追跡という言葉にかかっているんです。正確に目的語や助詞を入れてあげるとわかりやすいです。リモート追跡ブランチは、『リモートブランチを追跡するブランチ』ということです。
このようにリモートブランチを追跡してくれているブランチがあるから、リモート側とローカル側の両方に差分があった時などにエラーが出てpullできない状況を生み出したりします。このpullできない状況になった時の解決策はまた後述します。
コマンドとして纏めると以下のようになります。mergeコマンドの引数になっているorigin/masterがリモート追跡ブランチを意味し、リポジトリ名/ブランチ名で表される。リポジトリ名はデフォルトのoriginです。
$ git pull・・・ リモートブランチからリモート追跡ブランチ(上流ブランチ)にファイルを落とした後にリモート追跡ブランチからローカルリポジトリにファイルを落としてマージする
$ git fetch origin branch_name ・・・リモートブランチからリモート追跡ブランチにファイルを落とす
$ git merge origin/branch_name ・・・リモート追跡ブランチからローカルリポジトリにファイルを落としてマージする
この時点で、思ったのは、pullとfetchアンドmergeの使い分けについてです。コミットは、ハッシュで持ってくれているから、mergeさせても切り戻そうと思えば切り戻せるので、別にmergeさせても問題は無い訳なので、どのタイミングで使い分けるのだろうということです。
現時点での使い分けは、リモートリポジトリの更新内容を確認したい時にfetcheを使っています。他の人が自身が切ったブランチに対して何かしらマージを行なった場合、それをこちらでも取り込んでおく必要があるからです。それ以外は、普通にpullを使用する形で良いのでは無いかということです。
またここの話題については進展があったら追記や修正を行なっていきたいと思います。
競合した時の対処する際にこの概念を押さえておけば、後は切り戻すためのコマンドや、退避コマンドを知っておけば問題なく対処できると思います。競合時に使用頻度の増えるコマンドはまた別記事にまとめていきたいと思います。
常にイメージすべきはリモートとローカル
リモート追跡ブランチにもローカルがあるので、ローカルのリモート追跡ブランチから、リモートにある(originへの)追跡ブランチへのpushを忘れないこと。