3/27 夜会話(Git/Githubについて)

となりの人に色々教えてもらう。
本日はgitのリモートリポジトリを用いた運用概念について。
正直なところ、単一リポジトリしか使ってなかったので、よいお勉強。

始まりはfork

リモートのお話。
リポジトリをforkするといわれて分からなかったのがそもそもの始まり。
forkは他のリポジトリのプロジェクトを、自分のリポジトリのプロジェクトとしてコピーしてくること。(でいいのかな?)
あくまでリポジトリ間の処理であるので、(ローカル)ユーザのコマンドではない。
github上でもforkを実行するには、forkボタンをぽちっと押す事で問題を解決する。


他の人のリポジトリから直接cloneすることは可能。
すると、ローカルではコミットできる。ただし、それをpushすることは基本的にはできない。
(collaborators 権限を付けることで可能にはなる、が見ず知らずの人に対して付与するものではない。)
しかし、自分の作業用リモートリポジトリにforkして、それをcloneすれば、pushは可能になる。


pushを行った後、(githubでは)pull requestを送ることで、fork元の人にmergeの希望を提出することができる。
リクエスト(この人から、このリポジトリの、このブランチという情報)を受け取ったのち、fork元のリモートリポジトリの管理者は、リクエスト情報を元に変更を取り込むことができる。
fork元リポジトリの管理者が、ローカルリポジトリにリクエスト内容をfetchしてきて、それをリモートに反映させるという作業を行うことで、処理の取り込みを完了させる。


ここの処理は基本的には手動になるが、Jenkinsなどを利用して自動化する運用も可能。

Fast-Forward Merge

ローカルのお話。
あるコミットグラフに対してマージを行う場合、新規にコミットオブジェクトを作成しないマージをFast-Forward Merge(FF Merge)と言う。
具体的には、次のコミットグラフがある場合を考える。

a - b - c
------------> time
branches
 - master: b
 - develop: c

2個のブランチがあり、masterブランチはコミットオブジェクトbを、developブランチはコミットオブジェクトcを指している。
また、コミットオブジェクトはa,b,cの順に作成されている。

developブランチをmasterブランチにマージする場合、その結果は次のようになる。

a - b - c

branches
 - master: c
 - develop: c

このマージは、

  • masterブランチが指すコミットオブジェクトを変更しているだけであり、新規のコミットオブジェクトを作成しないため高速(Fast)
  • もとのコミットオブジェクトよりも新しく作成されたオブジェクトを指すように変更しているだけである(Forward)

の特性を持つ。


これをうまい事使うことで、1本道のきれいなコミットグラフができるセントラルリポジトリの運用もできるそうだ。

pull, fetch, remote-update

pushはローカルリポジトリから、リモートリポジトリへの反映次に使用する。
その反対、リモートリポジトリからローカルリポジトリへの反映には3種類のコマンドを使用できる。


pullは他のコマンドと異なり、マージを行い、コミットグラフのブランチが移動する。
fetch, remote-updateはマージを行わず、コミットグラフのブランチが移動しない。
ここで、次のコミットグラフを考える

[remote]
a - b

branches
 - master: b
------------------------
[local]
a

branches
 - master: a
 - origin/master: a

remoteリポジトリのコミットオブジェクトがaしかないときにlocalにcloneを行い、
時間が経過して誰かがbをpushした場合にこのようなコミットグラフができる。


この時、ローカルリポジトリでpullとfetchを実行すると、次の異なる結果が得られる。

[local - pull]
a - b

branches
 - master: b
 - origin/master: b
[local - push]
a - b

branches
 - master: a
 - origin/master: b

ここで、origin/masterというブランチが移動している点に疑問を持ったが、
xxx/yyyという形のブランチ(表現は他にあるのかも、ただ、githubでcloneしてくるときはorigin/masterを見ることが多い)は、リモートリポジトリのブランチの位置を指示している。
そのため、ローカルリポジトリをリモートリポジトリの情報で更新すると、自動的(?)に反映される。


ちなみに、1本筋のコミット履歴を作る場合、pullは怖い存在だそうだ。
なぜなら、次のような場合が考えられるためだ。

[remote]
a - b

branches
 - master: b
------------------------
[local]
a - c

branches
 - master: c
 - origin/master: a

この場合にfetchを行うと、ローカルには次のように取り込まれる。

[local]
a - c
 |- b
branches
 - master: c
 - origin/master: b

aを起点として、2つに分岐したコミットグラフが作成される。
さて、fetchの場合はこれで終了し、rebaseを行う事で履歴を1本道にできるのだが、
pullを用いた場合、さらにmergeが行われる。
そのため、次のようになる。

[local]
a - c - d
 |- b -|

branches
 - master: d
 - origin/master: b

マージにより、新たなコミットオブジェクトdが作成され、ルートが分かれたままになってしまう。
そのため、rebaseを使用する履歴を1本筋にする運用をするなら、pullは使わない方がよいみたい。


ついで、remote-updateは、基本的にはfetchの連続処理を行う。
clone元に対して処理を行うのはともかく、それ以外のリモートリポジトリの変更も同時に取り込む。
それ以外のリポートリポジトリはコマンドを打つローカル環境に登録しておくことで知ることができる。
リモートリポジトリの登録コマンドは git remote add である。
登録していなければ、いくらfork元だろうが対象にならない(ハズ)。
複数人で開発している時を想定すると、remote-updateの方が便利そうではある。

その他

分散管理型バージョン管理とは言っても、最終的にはセントラルリポジトリへのコミットは誰かが決定しなくてはならない。
セントラルリポジトリへの書き込み権限は基本的に1部の人しかないので、その人(等)が判断を行う必要がある。
Jenkinsなどで自動化しておくことも可能(ぐるぐる先生のJenkins/Git運用など)。