Git(ギット)勉強会メモ

さすがに開発者は的確な解説と、質問にもズバズバ答えるのだなぁ。
Gitのメンテナが日本人の方だったというのは、今回はじめて知りました。
プレゼン資料については、アメリカに帰られたらWebで公開されるということでしたが、PDFで分けてもらったので、ご希望の方がいれば送ります。中身は英語ですが(^^;。
# さっそくアップロードされたようです。http://www.kernel.org/~junio/200810-tut.pdf


というわけで、twiterでメモを流していたけど、途中で挫折したので、手元のメモを貼っておきます。
間違いがあったら指摘してね。
自分用メモなので、意味不明なとこはご勘弁を。


gitギット。イギリス英語で「やなやつ」。
kernel bitkeeper商用のバージョン管理システムを使っていた。
方針がかわって、無料で使えなくなってしまった。
svnを使うの?cvsを使うの?使い物にならないというコメント。
2W linusが休暇。
20050407にリリース。
これは面白いといってあちこちから参加があった。
kernelの人とは何のつきあいもなかったが、プロジェクトに参加した。
7月くらいにはかなり使い物になるコードになった。半分くらいはlinusで、半分くらいは講演者。
kernelに戻るから、あとは引き継いでよろしくと言われてやっている。


Wine, X.org, OLPC, Sambaなどで採用している。
RoRRuby系の人が今年増えた。


gitは、大きなシステム。
コマンドが150あまりある。全部manがある。
生活に必要なコマンドは20くらい。
ボトムアップで作成したので、小さなバラバラのコマンドを組み合わせられている。
checkinのための細かいコマンドがエンドユーザーに見えるようになっている。
大方のコマンドは考えなくてもよい。
概念。
基本はコンテンツをオブジェクトデータベースに入れる。
記録内容のハッシュ値を記録してコリジョンが起こらないことを前提。
起きたらその時考える。
ハッシュを送ると、そのときのファイルを返してくれる。


どこに何があるかも記録しておかないといけない。
tree。ファイルの構造とblob(SHA-1でのハッシュ値)の対応表。
その対応表もhash値を持つ。
1つのプロジェクトの内容が単一のハッシュ値で160bitで管理される。
さらに、上のレイヤーcommit。treeのメタデータとして、人といつ、コメントも記録する。


さらに上。別のtreeを作る。
前の状態。歴史情報としてバックポインタでもとのオブジェクトを指定するメタデータも持つ。
バージョン間の変遷も記録。
Blob, tree, commit, repository
最新の状態についてのポインタがcommitオブジェクトに付いている。


歴史は一直線ではない。
歴史に複数の先端ができる。
分散で開発される。
発散しないように、ある程度のところでマージして収束する。
変遷と親子関係を記録して、歴史をのばして行く。


前の状態を振り返ってみたいときも逆にたどれば変更の理由を見ることができる。
三ヶ月前の自分は自分ではないから。


歴史の先頭にあるコミットはHEADと呼ばれる。
ステージングエリア。HEADとWork treeの中間でバッファとしてindexがある。
チェックアウト後、work tree変更。変更をindexに戻す。
indexへの書き戻しは直した部分をもとにtreeを作ってヘッドポインタを付け替えHEADに。コミット。


バッファエリアはなんで必要?
cvssvnには概念はない。
バージョン管理されているファイルと、そうでないファイルがある。
中間的なコンパイル結果など管理されてない。
管理しているファイルのリストはバージョン管理システムが持っている。
ファイル名だけで内容は記録されていない。
名前だけでなはなく内容まで書いてしまおう。-> index。
このファイル、今はこういう内容だということを記録したファイル。
プロダクトに入れたくないが、開発に使っているファイル。
コミットする分だけindexに入れる。


これまでは、先頭とのdiffしか見えない。
変更していくと、自分のやった変更がたまる。
バッファエリアに入ったものは、diffで出てこないので、フォーカスしている部分だけのdiffを見ることができる。
index-HEADの変更も同様に管理できる。全体がどうかわったのかおさらいできる。
こきざみに丁寧に開発できる。


分散管理。
チェックアウトした先と同じコピーを全部ローカルに持っている。
履歴情報も含めて。
一度cloneしてきたら、手元の環境でしかコミットは起こらない。
パブリッシュしないと大本にマージはされない。
うまく行きそうだからと少しづつコミットしていくが、手戻りして、これまでのものを破棄しなければいけないこともある。
自分の手元でfixするまでは人に見せない。完璧な結果をマージできる。


git clone git://.... my-git
cd my-git
edit README
git add -p
add 変更を次のコミットに入れる 。

  • a はツリー全部。
  • pは細かいレベル:差分を表示して入れる部分をインタラクティブにほしいものだけバッファエリアに入れる。

ファイル名でもよい。patch形式のp。patchの固まりごとにy/nで決められる。
git diff
git diff --cached いままでにコミットに入れると行ったパッチを表示。
git commit -a


git diff --color-words 単語単位の違いを見せてくれる。patchコマンドでは使えないが。


git stash
日本からやってきたパッチ。最近追加。
する直せ、今直せとボスが言う。
中断されると違う事やると思考の流れが止まる。続けられない。
git stash saveとたたくと、今の状態を保管する。
巻き戻されて最初からボスの変更だけやって、コミットしてしまう。
その後で、git stash applyで保管した結果を戻して作業ができる。
tarで保管するのと違って、重なる変更点は3 way margeされる。
ボスの変更が反映された上で、途中の作業に戻れる。


git stash save --keep-index
三ヶ月くらい前につくった。
中断した時にどうするというstashから、
デバッグ用のコードなども
indexからコミットするというのは、実用上それでいいのかという場面がないわけではない。
デバッグコードがタイミングを変えているようなこともよくある。
コミット前に最後にテストしたい。
keep-indexすると、コミットしようとしていたものだけど入れて、デバッグコードなどは捨てた状態ができる。
そこでテストして自信を持ってコミットする。
デバッグ用の変更も便利なので後々使いたい。stash applyするとデバッグコードが戻る。
ドイツ人が書いた。


資料を希望してみる。
アメリカの帰ったらWebに上げるとのこと。
USBメモリで回覧してもらった。


git rebase --interactive
gitは、こそっとやって失敗を人にみせなくてもいいツール。
A->B->C->D
Bでtypoがあった。
$edit
$ git commit -m Typofix
A->B->C->D->E
$ git rebase -i HEAD~4
HEADから4つ前から先を全部書き換える。
出てくるリストを編集して順番を入れ替える。
squash -hash- Typofix
これまでの変更を再構築してくれる。
A->B'->C'->D'になる。
それをコミットすればよい。
時系列で問題点を洗い出すには、しょうもない変更による手戻り点を記録する意味はない。


ここからは、みんなで分散開発する話。
cloneする。元でも変更があり、自分でも変更をしている。
fetchでオリジナルの変更をコピーしてくる。
maegeして、自分のHEADとオリジナルのHEADをあわせて新しいHEADを作る。
オリジナルの人はは経過がわからない。pushして自分の履歴を送る。
2人ならいいが、複数人では使えない。
セントラルリポジトリを用意して、皆そこと同期する。


セントラルリポジトリを持たずにやる方法。
どのようなタイミングで誰が歴史をどこにコピーするかが違う。
linux kernelで使われている。
新しいパッチを受け取る側が能動的に取りに行く。
コミッタがマージをする。
プロジェクトの性格によって運用が変えられる。


gitそのものの開発。
NotePCとDesktopPC。どちらもメイン。同期する。
linux kernel。自分の作業リポジトリと、パブリックリポジトリを作る。
パブリック同士で相互にpush/pullする。


2つの側面。
戻る、どうして戻りたい。
歴史をほじくり返すのは重要なこと。
gitK, git-log。コミット間の親子関係。線の色は意味がない。区別するためだめ。


git bisect
どこでプログラムが壊れたのかを見つけるコマンド。
1つしか壊れていない状態を前提。
git bisect run
スクリプトを食わせてやるとコード中のテストスイートを自動実行し結果を確認していく。
1年半くらい前にできた。


git blame (cvsのanotateのこと)
それぞれの行について、どのバージョンで追加されたのかわかる。
コメントやコミッタも。
1.3あたりで使い物になるようになった。
このくらいは普通にどれもできる。
ファイルパスが変更された場合も追跡できる。
gitでは、ファイルと配置情報が別々に管理されているので、オリジナルのファイルを追える。
git blame -M lib/xyzzy.c
ファイル内で関数の前後が入れ替わった場合でも追える。
全体をdiffすると、めんどくさいことになる。
コードの部分が動いた部分も追える。
CPUリソースを食うので、オプションが付けられている。


git blame -C lib/xyzzy.c
1つのファイルが複数の起源を持っているような場合にも対応できる。
こちらもリソース食いなのでオプション。
cvsクライアントが対応できないこともあるので、オプションになっている。


URLなど。


ここからデモ。


gitは、コミットは皆がすることができるのが生得の権利と考える。
伝達してアップストリームに持って行く手段はいろいろある。


git checkout -b bia8236 master
ブランチを作るのもチェックアウト。
編集。
git diff で変更点がでる。
コミットする。
git commit -a -s -v

  • s サインオフ。自分が著作権があるという署名。
  • v diffをもう一度確認できる。

エディタが起動してdiffが出る。
コメントをはじめのほうの行に書き込む。
git show
コミットの内容が見える。
git grep -n -e VIA --and -e '823[56]'とか
git grep -n -e 'VIA823[56]' -- arch/i386 include/linux
git の管理対象だめgrepしてあげる。
たりてなさそう。
コミットしちゃった。
git commit --amend -a
前の修正コミットを書き換えてしまえる。
前のは残ってしまうが、ガーベージコレクタが3ヶ月くらいで消去。消されるまでは戻れる。
git checkout master
マスターブランチに戻る。


メール形式に出す。
git format-patch master..via8236
masterからviaまでの間のパッチを出す。
メールに貼って送ればよい。


メールを取り込む。
git checkout 0b spell master
git am -i file...
インタラクティブに適用。
確認。編集。


masiterから2つのブランチ。
git checkout masiter
git marge spell via8636
HEADにマージされる。
オクトパスマージ。


マスタリポジトリが更新されている場合。
git pull マスタの変更を持ってくる。
マージコンフリクトが起こっている。
編集して直す。
git diff で確認。
コンフリクト解決した内容をコメント。


質問タイム。


ファイルはどのように保存されているのか。
ファイルはpackフォーマットで保存されている。
そのまま個別に圧縮するシミュレーション。年間3GBくらい増える。gitが公開されて一週間くらいで。
差分をしよう。
差分のオリジナルのポインタと差分ファイルにしてみた。バラバラのファイルで管理。
pack形式で同じようなファイルを1つのファイルに押し込んで、インデックスファイルを別に持つようになった。05/07くらい。
gitはblobには名前がない。
ボイラープレート:同じ内容があちこちに。
違うファイルでも、似た者は差分で保管。圧縮率がとてもよい。
テキストもバイナリも区別していない。


cloneはフルクローンなので時間がかかる。
OOoのドキュメントはzipアーカイブ。フィルタをかけてunzipして圧縮なしで保管しなおしてgitで管理している。


cloneの時リモートリポジトリのmaster?
全てのブランチを持ってくる。
リモートのHEADのブランチと同じブランチをローカルに作る。


マスタリポジトリのブランチをクローンしてくるようなことは?
全てのブランチを持ってくるので、ローカルでcheckoutして戻ればいい。
そういうのが仕様。歴史的な経緯で。
どうしてもなら、cloneにnocheckuotフラグがあるので、それでやって、自分の好きなものをcheckoutする。


ローカルで複数のブランチを作業したい。
ワーキングツリーのみを複数持つことは?
cloneを2つでリポジトリはハードリンクで作れる。
newworkdirがコントリビュートにある。これを使うと別のワークツリーが作れる。
buildtest用に使っている。


プロトコルの特徴。
gitプロトコルが使える人は、それを使うべき。
専用なので効率がいい。
git native git://〜 anon readonly。sshでアクセスで相手がわかる方法もある。
最小のケースでできるように通信して同期できる。
http 動きが逆向き。コミットを順番に歩く、ピンポンしてレイテンシが悪い。
若干メンテナンスが放置されている。pushがかなり新しいcurlライブラリとでないと動かない。
DAVを使っているので、サーバーを選んだりする。
gitを使うのが安全。


公開サーバー上にリポジトリを置いている場合。
不要なブランチ。たまにログインしてgcする必要があるのか?
ログインしないリポジトリは、フックがあるので使う。
pushの直前や結果に応じたフックができる。
post update hookを使うとよいだろう。そこでgcを自動で走らせるといいだろう。
使っていないブランチって、無駄としては48byteなのでたいしたことはない。
見た目は悪くなる。リモートのブランチをローカルから削除できるようにもなっている。


差分3回コミット時は?
リビジョン的な考え方はやめたほうがいい。
最新なものから過去のものを作るのがコストが安い。
基本的にはコードは増える方向になるから。
記憶量によって相互比較してサイズの小さいものを選ぶようになっている。
差分を計算するコストはかかる。パラメーターで指定できる。何個まで比較ウインドを持つか。
gcの中でオフラインでやる。バッチでやったり、packdでやることもできる。


二人同時プッシュ。
楽観的なロックになっている。
HEADの変化の場合だけがロックになっている。
失敗すると再度プッシュをする必要がある。
アトミック性:アトシティは保証されている。
バージョン番号という考えは成り立たないが、セントラルリポジトリで新しい履歴の先頭は前の履歴の子供でないとダメ。
違うもののコンフリクトを防げる。