-
1. Gitを始めるにあたって
- 1.1 バージョン管理について
- 1.2 Gitの歴史
- 1.3 Gitとは何か?
- 1.4 コマンドライン
- 1.5 Gitのインストール
- 1.6 Gitの初回セットアップ
- 1.7 ヘルプの利用
- 1.8 まとめ
-
2. Gitの基本
- 2.1 Gitリポジトリの取得
- 2.2 リポジトリへの変更の記録
- 2.3 コミット履歴の表示
- 2.4 元に戻す操作
- 2.5 リモートでの作業
- 2.6 タグ付け
- 2.7 Gitエイリアス
- 2.8 まとめ
-
3. Gitのブランチ機能
- 3.1 ブランチの基本
- 3.2 基本的なブランチとマージ
- 3.3 ブランチ管理
- 3.4 ブランチワークフロー
- 3.5 リモートブランチ
- 3.6 リベース
- 3.7 まとめ
-
4. サーバー上のGit
- 4.1 プロトコル
- 4.2 サーバーにGitをセットアップする
- 4.3 SSH公開鍵の生成
- 4.4 サーバーのセットアップ
- 4.5 Gitデーモン
- 4.6 スマートHTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 サードパーティのホスティングオプション
- 4.10 まとめ
-
5. 分散Git
- 5.1 分散ワークフロー
- 5.2 プロジェクトへの貢献
- 5.3 プロジェクトの管理
- 5.4 まとめ
-
6. GitHub
- 6.1 アカウントのセットアップと設定
- 6.2 プロジェクトへの貢献
- 6.3 プロジェクトの管理
- 6.4 組織の管理
- 6.5 GitHubのスクリプト
- 6.6 まとめ
-
7. Gitツール
- 7.1 リビジョンの選択
- 7.2 インタラクティブステージング
- 7.3 スタッシュとクリーン
- 7.4 作業に署名する
- 7.5 検索
- 7.6 履歴の書き換え
- 7.7 Resetの解明
- 7.8 高度なマージ
- 7.9 Rerere
- 7.10 Gitを使ったデバッグ
- 7.11 サブモジュール
- 7.12 バンドル
- 7.13 置換
- 7.14 認証情報の保存
- 7.15 まとめ
-
8. Gitのカスタマイズ
- 8.1 Gitの設定
- 8.2 Git属性
- 8.3 Gitフック
- 8.4 Gitによるポリシー適用例
- 8.5 まとめ
-
9. Gitと他のシステム
- 9.1 クライアントとしてのGit
- 9.2 Gitへの移行
- 9.3 まとめ
-
10. Gitの内側
- 10.1 PlumbingとPorcelain
- 10.2 Gitオブジェクト
- 10.3 Gitリファレンス
- 10.4 Packfile
- 10.5 Refspec
- 10.6 転送プロトコル
- 10.7 メンテナンスとデータ復旧
- 10.8 環境変数
- 10.9 まとめ
-
A1. 付録A: 他の環境でのGit
- A1.1 グラフィカルインターフェース
- A1.2 Visual StudioでのGit
- A1.3 Visual Studio CodeでのGit
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMineでのGit
- A1.5 Sublime TextでのGit
- A1.6 BashでのGit
- A1.7 ZshでのGit
- A1.8 PowerShellでのGit
- A1.9 まとめ
-
A2. 付録B: アプリケーションへのGitの組み込み
- A2.1 コマンドラインGit
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. 付録C: Gitコマンド
- A3.1 セットアップと設定
- A3.2 プロジェクトの取得と作成
- A3.3 基本的なスナップショット
- A3.4 ブランチとマージ
- A3.5 プロジェクトの共有と更新
- A3.6 検査と比較
- A3.7 デバッグ
- A3.8 パッチ適用
- A3.9 メール
- A3.10 外部システム
- A3.11 管理
- A3.12 Plumbingコマンド
10.6 Git内部 - 転送プロトコル
転送プロトコル
Gitは2つのリポジトリ間でデータを転送する方法として、主に「ダムプロトコル」と「スマートプロトコル」の2つがあります。このセクションでは、これら2つの主要なプロトコルがどのように機能するかを簡単に説明します。
ダムプロトコル
HTTP経由で読み取り専用のリポジトリを設定する場合、おそらくダムプロトコルが使用されます。このプロトコルは、転送プロセス中にサーバー側でGit固有のコードを必要としないため「ダム(dumb)」と呼ばれます。フェッチプロセスは一連のHTTP GET
リクエストであり、クライアントはサーバー上のGitリポジトリのレイアウトを想定できます。
注
|
ダムプロトコルは最近ではあまり使われていません。セキュリティを確保したり非公開にしたりするのが難しいため、ほとんどのGitホスト(クラウドベースとオンプレミス双方)はこれの使用を拒否します。後で説明するスマートプロトコルを使用することが一般的に推奨されています。 |
simplegitライブラリのhttp-fetch
プロセスを追ってみましょう。
$ git clone http://server/simplegit-progit.git
このコマンドが最初に行うのは、info/refs
ファイルの取得です。このファイルはupdate-server-info
コマンドによって書き込まれるため、HTTP転送を正しく機能させるには、これをpost-receive
フックとして有効にする必要があります。
=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949 refs/heads/master
これでリモート参照とSHA-1のリストができました。次に、HEAD参照が何であるかを確認し、終了時に何を確認すればよいかを知ります。
=> GET HEAD
ref: refs/heads/master
プロセスが完了したら、master
ブランチをチェックアウトする必要があります。この時点で、ウォークプロセスを開始する準備ができています。開始点はinfo/refs
ファイルで確認したca82a6
コミットオブジェクトなので、まずそれをフェッチします。
=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)
オブジェクトが返されます。そのオブジェクトはサーバー上にルーズ形式で存在し、静的なHTTP GETリクエスト経由でフェッチしました。それをzlibで解凍し、ヘッダーを削除して、コミットの内容を確認できます。
$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@gmail.com> 1240030591 -0700
Change version number
次に、さらに2つのオブジェクトを取得する必要があります。cfda3b
は、取得したばかりのコミットが指しているコンテンツのツリーであり、085bb3
は親コミットです。
=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)
これで次のコミットオブジェクトが得られます。ツリーオブジェクトを取得します。
=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)
おっと、そのツリーオブジェクトはサーバー上にルーズ形式で存在しないようで、404応答が返されました。これにはいくつかの理由があります。オブジェクトが別のリポジトリにあるか、このリポジトリのパックファイルにある可能性があります。Gitはまずリストされている代替リポジトリをチェックします。
=> GET objects/info/http-alternates
(empty file)
これが代替URLのリストを返した場合、Gitはそこのルーズファイルとパックファイルをチェックします。これは、互いにフォークしているプロジェクトがディスク上のオブジェクトを共有するための優れたメカニズムです。しかし、この場合は代替がリストされていないため、オブジェクトはパックファイルに存在する必要があります。このサーバーで利用可能なパックファイルを確認するには、それらのリストを含むobjects/info/packs
ファイル(これもupdate-server-info
によって生成されます)を取得する必要があります。
=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
サーバー上にはパックファイルが1つしかないので、オブジェクトは明らかにそこに含まれていますが、念のためインデックスファイルを確認します。これは、サーバー上に複数のパックファイルがある場合にも役立ち、必要なオブジェクトが含まれているパックファイルを特定できます。
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)
パックファイルインデックスがあれば、オブジェクトがそこにあるかを確認できます。インデックスには、パックファイルに含まれるオブジェクトのSHA-1と、それらのオブジェクトへのオフセットがリストされているからです。オブジェクトはそこにあるので、パックファイル全体を取得します。
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)
ツリーオブジェクトがあるので、コミットのウォークを続行します。これらもすべて、ダウンロードしたばかりのパックファイル内にあるため、サーバーへの追加のリクエストは不要です。Gitは、最初にダウンロードしたHEAD参照が指していたmaster
ブランチのワーキングコピーをチェックアウトします。
スマートプロトコル
ダムプロトコルは単純ですが少し非効率的であり、クライアントからサーバーへのデータの書き込みを処理できません。スマートプロトコルは、より一般的なデータ転送方法ですが、リモート側でGitについてインテリジェントなプロセスを必要とします。つまり、ローカルデータを読み取り、クライアントが持っているものと必要なものを把握し、それに応じたカスタムのパックファイルを生成できます。データ転送には2組のプロセスがあります。データをアップロードするためのペアと、データをダウンロードするためのペアです。
データアップロード
リモートプロセスにデータをアップロードするために、Gitはsend-pack
とreceive-pack
プロセスを使用します。send-pack
プロセスはクライアント上で実行され、リモート側のreceive-pack
プロセスに接続します。
SSH
たとえば、プロジェクトでgit push origin master
を実行し、origin
がSSHプロトコルを使用するURLとして定義されているとします。Gitはsend-pack
プロセスを起動し、SSH経由でサーバーへの接続を開始します。それは、次のようなSSH呼び出しを通じてリモートサーバー上でコマンドを実行しようとします。
$ ssh -x git@server "git-receive-pack 'simplegit-progit.git'"
00a5ca82a6dff817ec66f4437202690a93763949 refs/heads/master□report-status \
delete-refs side-band-64k quiet ofs-delta \
agent=git/2:2.1.1+github-607-gfba4028 delete-refs
0000
git-receive-pack
コマンドは、現在持っている参照ごとに1行で即座に応答します。この場合、master
ブランチとそのSHA-1のみです。最初の行には、サーバーの機能のリスト(ここではreport-status
、delete-refs
、およびクライアント識別子を含むその他の機能)も含まれます。
データはチャンクに分割されて送信されます。各チャンクは、チャンクの長さ(それ自体の4バイトを含む)を指定する4文字の16進値で始まります。チャンクは通常、1行のデータと末尾の改行を含みます。最初のチャンクは00a5で始まり、これは16進数で165を意味し、チャンクが165バイト長であることを示します。次のチャンクは0000で、サーバーが参照リストの処理を終えたことを意味します。
サーバーの状態がわかったので、send-pack
プロセスはサーバーが持っていないコミットを特定します。このプッシュで更新される各参照について、send-pack
プロセスはその情報をreceive-pack
プロセスに伝えます。たとえば、master
ブランチを更新し、experiment
ブランチを追加する場合、send-pack
の応答は次のようになります。
0076ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 \
refs/heads/master report-status
006c0000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d \
refs/heads/experiment
0000
Gitは、更新する各参照に対して、行の長さ、古いSHA-1、新しいSHA-1、および更新される参照を含む行を送信します。最初の行にはクライアントの機能も含まれます。すべての'0'のSHA-1値は、以前には何もなかったことを意味します。なぜならexperiment
参照を追加しているからです。参照を削除する場合は、逆のことが起こります。右側にすべての'0'が表示されます。
次に、クライアントはサーバーがまだ持っていないすべてのオブジェクトのパックファイルを送信します。最後に、サーバーは成功(または失敗)の表示で応答します。
000eunpack ok
HTTP(S)
このプロセスはHTTP経由でもほとんど同じですが、ハンドシェイクが少し異なります。接続は次のリクエストで開始されます。
=> GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack
001f# service=git-receive-pack
00ab6c5f0e45abd7832bf23074a333f739977c9e8188 refs/heads/master□report-status \
delete-refs side-band-64k quiet ofs-delta \
agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e
0000
これで最初のクライアントとサーバー間の交換は終了です。クライアントは次に、send-pack
が提供するデータを含む別のリクエスト(今回はPOST
)を発行します。
=> POST http://server/simplegit-progit.git/git-receive-pack
POST
リクエストには、send-pack
の出力とパックファイルがペイロードとして含まれます。サーバーは、HTTP応答で成功または失敗を示します。
HTTPプロトコルは、このデータをチャンク転送エンコーディングでさらにラップする可能性があることに注意してください。
データダウンロード
データをダウンロードする場合、fetch-pack
とupload-pack
プロセスが関与します。クライアントはfetch-pack
プロセスを開始し、リモート側のupload-pack
プロセスに接続して、どのデータを転送するかをネゴシエートします。
SSH
SSH経由でフェッチを行う場合、fetch-pack
は次のように実行されます。
$ ssh -x git@server "git-upload-pack 'simplegit-progit.git'"
fetch-pack
が接続した後、upload-pack
は次のようなものを返します。
00dfca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \
side-band side-band-64k ofs-delta shallow no-progress include-tag \
multi_ack_detailed symref=HEAD:refs/heads/master \
agent=git/2:2.1.1+github-607-gfba4028
003fe2409a098dc3e53539a9028a94b6224db9d6a6b6 refs/heads/master
0000
これはreceive-pack
が応答するものと非常によく似ていますが、機能が異なります。さらに、HEADが何を指しているか(symref=HEAD:refs/heads/master
)を返すため、クライアントはこれがクローンである場合に何をチェックアウトすればよいかを知ることができます。
この時点で、fetch-pack
プロセスは自身が持っているオブジェクトを確認し、「want」とそれに続く必要なSHA-1を送信することで、必要なオブジェクトを応答します。自身がすでに持っているすべてのオブジェクトを「have」とそれに続くSHA-1で送信します。このリストの終わりに、「done」を書き込み、upload-pack
プロセスが、必要なデータのパックファイルの送信を開始するよう指示します。
003cwant ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0009done
0000
HTTP(S)
フェッチ操作のハンドシェイクには2つのHTTPリクエストが必要です。最初のものは、ダムプロトコルで使用されたのと同じエンドポイントへのGET
です。
=> GET $GIT_URL/info/refs?service=git-upload-pack
001e# service=git-upload-pack
00e7ca82a6dff817ec66f44342007202690a93763949 HEAD□multi_ack thin-pack \
side-band side-band-64k ofs-delta shallow no-progress include-tag \
multi_ack_detailed no-done symref=HEAD:refs/heads/master \
agent=git/2:2.1.1+github-607-gfba4028
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
0000
これはSSH接続を介してgit-upload-pack
を呼び出すのと非常に似ていますが、2番目の交換は別のリクエストとして実行されます。
=> POST $GIT_URL/git-upload-pack HTTP/1.0
0032want 0a53e9ddeaddad63ad106860237bbf53411d11a7
0032have 441b40d833fdfa93eb2908e52742248faf0ee993
0000
繰り返しになりますが、これは上記と同じ形式です。このリクエストへの応答は成功または失敗を示し、パックファイルを含みます。
プロトコル概要
このセクションには、転送プロトコルの非常に基本的な概要が含まれています。プロトコルにはmulti_ack
やside-band
機能など、他にも多くの機能が含まれていますが、それらすべてをこの本でカバーすることはできません。私たちはクライアントとサーバー間の一般的なやり取りの感覚を提供しようとしました。これ以上の知識が必要な場合は、Gitのソースコードを参照することをお勧めします。