-
1. はじめに
- 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 Daemon
- 4.6 Smart 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 リセットの解説
- 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 プラミングとポーセリン
- 10.2 Gitオブジェクト
- 10.3 Git参照
- 10.4 パックファイル
- 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 プラミングコマンド
10.2 Git内部構造 - Gitオブジェクト
Gitオブジェクト
Gitはコンテンツアドレス可能なファイルシステムです。素晴らしい。それはどういう意味でしょうか?これは、Gitの中核はシンプルなキーと値のデータストアであるということです。つまり、Gitリポジトリに任意の種類のコンテンツを挿入でき、Gitはそのコンテンツを取得するために後で使用できる一意のキーを返します。
デモンストレーションとして、プラミングコマンド`git hash-object`を見てみましょう。このコマンドは、いくつかのデータを受け取り、それを`.git/objects`ディレクトリ(*オブジェクトデータベース*)に格納し、そのデータオブジェクトを参照するための一意のキーを返します。
まず、新しいGitリポジトリを初期化し、`objects`ディレクトリに(当然のことながら)何もないことを確認します。
$ git init test
Initialized empty Git repository in /tmp/test/.git/
$ cd test
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f
Gitは`objects`ディレクトリを初期化し、その中に`pack`と`info`のサブディレクトリを作成しましたが、通常のファイルはありません。次に、`git hash-object`を使用して新しいデータオブジェクトを作成し、新しいGitデータベースに手動で保存してみましょう。
$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
`git hash-object`は、最も単純な形式では、渡されたコンテンツを受け取り、Gitデータベースに保存するために使用される一意のキーを返すだけです。`-w`オプションは、単にキーを返すだけでなく、オブジェクトをデータベースに書き込むようにコマンドに指示します。最後に、`--stdin`オプションは、処理するコンテンツをstdinから取得するように`git hash-object`に指示します。そうでない場合、コマンドは、使用するコンテンツを含むコマンドの末尾にファイル名引数を予期します。
上記のコマンドの出力は、40文字のチェックサムハッシュです。これはSHA-1ハッシュです。保存しているコンテンツとヘッダーのチェックサムであり、これについては少し後で説明します。これで、Gitがデータをどのように保存したかを確認できます。
$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
再び`objects`ディレクトリを調べると、その新しいコンテンツのファイルが含まれていることがわかります。これは、Gitが最初にコンテンツを保存する方法です。コンテンツごとに1つのファイルとして保存され、コンテンツとそのヘッダーのSHA-1チェックサムで名前が付けられます。サブディレクトリはSHA-1の最初の2文字で名前が付けられ、ファイル名は残りの38文字です。
オブジェクトデータベースにコンテンツを入れたら、git cat-file
コマンドでそのコンテンツを調べることができます。このコマンドは、Gitオブジェクトを検査するための、いわばスイスアーミーナイフのようなものです。cat-file
に -p
を渡すと、まずコンテンツのタイプを調べてから、適切に表示するように指示します。
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
これで、Gitにコンテンツを追加して、再度取り出すことができるようになりました。ファイル内のコンテンツについても同様のことができます。たとえば、ファイルに対して簡単なバージョン管理を行うことができます。まず、新しいファイルを作成し、その内容をデータベースに保存します。
$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30
次に、ファイルに新しいコンテンツを書き込み、再度保存します。
$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
これで、オブジェクトデータベースには、この新しいファイルの2つのバージョン(およびそこに保存した最初のコンテンツ)が含まれるようになりました。
$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
この時点で、test.txt
ファイルのローカルコピーを削除し、オブジェクトデータベースから、保存した最初のバージョン
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1
または2番目のバージョンを、Gitを使って取得することができます。
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2
しかし、ファイルの各バージョンのSHA-1キーを覚えておくのは現実的ではありません。さらに、ファイル名はシステムに保存されておらず、コンテンツのみが保存されています。このオブジェクトタイプはblobと呼ばれます。git cat-file -t
を使用すると、SHA-1キーが与えられたGit内の任意のオブジェクトのオブジェクトタイプをGitに教えてもらうことができます。
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob
ツリーオブジェクト
次に調べるGitオブジェクトのタイプは、ツリーです。これはファイル名を保存するという問題を解決し、さらにファイルのグループを一緒に保存することを可能にします。GitはコンテンツをUNIXファイルシステムと似た方法で保存しますが、少し簡略化されています。すべてのコンテンツはツリーとblobオブジェクトとして保存され、ツリーはUNIXディレクトリのエントリに対応し、blobはほぼinodeまたはファイルコンテンツに対応します。1つのツリーオブジェクトには、1つ以上のエントリが含まれており、それぞれがblobまたはサブツリーのSHA-1ハッシュで、関連するモード、タイプ、ファイル名を持っています。たとえば、最新のツリーが次のようになっているプロジェクトがあるとします。
$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README
100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib
master^{tree}
という構文は、master
ブランチの最後のコミットが指しているツリーオブジェクトを指定します。lib
サブディレクトリはblobではなく、別のツリーへのポインターであることに注意してください。
$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb
注記
|
使用しているシェルによっては、 WindowsのCMDでは、 ZSHを使用している場合、 |
概念的には、Gitが保存しているデータは次のようになります。

独自のツリーをかなり簡単に作成できます。Gitは通常、ステージング領域またはインデックスの状態を取得し、そこから一連のツリーオブジェクトを書き込むことによってツリーを作成します。そのため、ツリーオブジェクトを作成するには、まずいくつかのファイルをステージングしてインデックスを設定する必要があります。単一のエントリ(test.txt
ファイルの最初のバージョン)を持つインデックスを作成するには、配管コマンドであるgit update-index
を使用できます。このコマンドを使用して、test.txt
ファイルの以前のバージョンを新しいステージング領域に人工的に追加します。ファイルはまだステージング領域に存在しないため(まだステージング領域を設定していないため)、--add
オプションを渡す必要があり、追加するファイルはディレクトリ内ではなくデータベース内にあるため、--cacheinfo
オプションを渡す必要があります。次に、モード、SHA-1、およびファイル名を指定します。
$ git update-index --add --cacheinfo 100644 \
83baae61804e65cc73a7201a7252750c76066a30 test.txt
この場合、100644
というモードを指定しています。これは、通常のファイルであることを意味します。他のオプションは、実行可能ファイルであることを意味する100755
と、シンボリックリンクを指定する120000
です。モードは通常のUNIXモードから取得されますが、柔軟性ははるかに低くなります。これらの3つのモードは、Gitのファイル(blob)に対して有効な唯一のモードです(ただし、ディレクトリとサブモジュールには他のモードが使用されます)。
これで、git write-tree
を使用して、ステージング領域をツリーオブジェクトに書き出すことができます。-w
オプションは必要ありません。このコマンドを呼び出すと、そのツリーがまだ存在しない場合、インデックスの状態からツリーオブジェクトが自動的に作成されます。
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
以前に見た同じgit cat-file
コマンドを使用して、これがツリーオブジェクトであることを確認することもできます。
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree
次に、test.txt
の2番目のバージョンと新しいファイルを使用して、新しいツリーを作成します。
$ echo 'new file' > new.txt
$ git update-index --cacheinfo 100644 \
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git update-index --add new.txt
これで、ステージング領域には、test.txt
の新しいバージョンと新しいファイルであるnew.txt
があります。そのツリーを書き出し(ステージング領域またはインデックスの状態をツリーオブジェクトに記録)、どのように見えるかを確認します。
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
このツリーには、両方のファイルエントリがあり、test.txt
のSHA-1が以前の「バージョン2」のSHA-1(1f7a7a
)であることに注意してください。面白半分に、最初のツリーをこのツリーのサブディレクトリとして追加します。git read-tree
を呼び出すと、ツリーをステージング領域に読み込むことができます。この場合、このコマンドで--prefix
オプションを使用すると、既存のツリーをサブツリーとしてステージング領域に読み込むことができます。
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
書き込んだ新しいツリーからワーキングディレクトリを作成した場合、ワーキングディレクトリの最上位レベルに2つのファイルと、test.txt
ファイルの最初のバージョンを含むbak
という名前のサブディレクトリが作成されます。Gitがこれらの構造に対して含むデータを、次のように考えることができます。

コミットオブジェクト
上記のすべてを実行した場合、追跡したいプロジェクトのさまざまなスナップショットを表す3つのツリーができたことになりますが、以前の問題が残っています。スナップショットを呼び出すためには、3つのSHA-1値をすべて覚えておく必要があります。また、誰がスナップショットを保存したか、いつ保存したか、なぜ保存したかについての情報もありません。これは、コミットオブジェクトが保存する基本情報です。
コミットオブジェクトを作成するには、commit-tree
を呼び出し、単一のツリーSHA-1と、直接先行するコミットオブジェクト(ある場合)を指定します。書き込んだ最初のツリーから始めます。
$ echo 'First commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
注記
|
作成時間と作成者データが異なるため、異なるハッシュ値が得られます。さらに、原則として、コミットオブジェクトは、そのデータが与えられれば正確に再現できますが、本書の構築の歴史的な詳細により、印刷されたコミットハッシュが与えられたコミットに対応しない可能性があります。この章では、コミットハッシュとタグハッシュを独自のチェックサムに置き換えてください。 |
これで、新しいコミットオブジェクトをgit cat-file
で調べることができます。
$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
First commit
コミットオブジェクトの形式はシンプルです。その時点でのプロジェクトのスナップショットの最上位ツリー、親コミット(もしあれば)(上記のコミットオブジェクトには親はありません)、作成者/コミッター情報(user.name
とuser.email
の設定とタイムスタンプを使用します)、空白行、そしてコミットメッセージを指定します。
次に、他の2つのコミットオブジェクトを書き込みます。それぞれが、その直前のコミットを参照します。
$ echo 'Second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo 'Third commit' | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9
3つのコミットオブジェクトはそれぞれ、作成した3つのスナップショットツリーの1つを指しています。奇妙なことに、最後のコミットSHA-1で実行すると、git log
コマンドで表示できる実際のGit履歴ができたことになります。
$ git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:15:24 2009 -0700
Third commit
bak/test.txt | 1 +
1 file changed, 1 insertion(+)
commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:14:29 2009 -0700
Second commit
new.txt | 1 +
test.txt | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:09:34 2009 -0700
First commit
test.txt | 1 +
1 file changed, 1 insertion(+)
すごいですね。フロントエンドコマンドを使用せずに、Git履歴を構築するための低レベルの操作を完了しました。これは、git add
およびgit commit
コマンドを実行するときにGitが行うことと基本的に同じです。変更されたファイルのblobを保存し、インデックスを更新し、ツリーを書き出し、最上位のツリーと直前に来たコミットを参照するコミットオブジェクトを書き出します。これらの3つの主要なGitオブジェクト(blob、ツリー、コミット)は、最初は.git/objects
ディレクトリに個別のファイルとして保存されます。これが、現在の例のディレクトリにあるすべてのオブジェクトであり、保存されている内容がコメントされています。
$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1
内部ポインタをすべてたどると、次のようなオブジェクトグラフが得られます。

オブジェクトストレージ
前に、Gitオブジェクトデータベースにコミットするすべてのオブジェクトにヘッダーが保存されると述べました。Gitがオブジェクトをどのように保存するかを少し見てみましょう。blobオブジェクト(この場合は、文字列「what is up, doc?」)をRubyスクリプト言語でインタラクティブに保存する方法を見ていきます。
irb
コマンドでインタラクティブなRubyモードを起動できます。
$ irb
>> content = "what is up, doc?"
=> "what is up, doc?"
Gitは最初に、オブジェクトのタイプ(この場合はblob)を識別することから始まるヘッダーを作成します。ヘッダーの最初の部分に、Gitはスペースを続け、コンテンツのサイズをバイト単位で追加し、最後にヌルバイトを追加します。
>> header = "blob #{content.bytesize}\0"
=> "blob 16\u0000"
Gitはヘッダーと元のコンテンツを連結し、その新しいコンテンツのSHA-1チェックサムを計算します。Rubyで文字列のSHA-1値を計算するには、require
コマンドでSHA1ダイジェストライブラリを含め、文字列を使用してDigest::SHA1.hexdigest()
を呼び出します。
>> store = header + content
=> "blob 16\u0000what is up, doc?"
>> require 'digest/sha1'
=> true
>> sha1 = Digest::SHA1.hexdigest(store)
=> "bd9dbf5aae1a3862dd1526723246b20206e5fc37"
これをgit hash-object
の出力と比較してみましょう。ここで、echo -n
を使用して、入力に改行が追加されないようにします。
$ echo -n "what is up, doc?" | git hash-object --stdin
bd9dbf5aae1a3862dd1526723246b20206e5fc37
Gitは、zlibを使用して新しいコンテンツを圧縮します。これは、zlibライブラリを使用してRubyで実行できます。まず、ライブラリをrequireする必要があり、次にコンテンツに対してZlib::Deflate.deflate()
を実行する必要があります。
>> require 'zlib'
=> true
>> zlib_content = Zlib::Deflate.deflate(store)
=> "x\x9CK\xCA\xC9OR04c(\xCFH,Q\xC8,V(-\xD0QH\xC9O\xB6\a\x00_\x1C\a\x9D"
最後に、zlibで圧縮されたコンテンツをディスク上のオブジェクトに書き込みます。書き出すオブジェクトのパス(SHA-1値の最初の2文字がサブディレクトリ名、最後の38文字がそのディレクトリ内のファイル名)を決定します。Rubyでは、FileUtils.mkdir_p()
関数を使用して、サブディレクトリが存在しない場合は作成できます。次に、File.open()
でファイルを開き、前にzlib圧縮したコンテンツを、結果のファイルハンドルでwrite()
を呼び出してファイルに書き出します。
>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]
=> ".git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37"
>> require 'fileutils'
=> true
>> FileUtils.mkdir_p(File.dirname(path))
=> ".git/objects/bd"
>> File.open(path, 'w') { |f| f.write zlib_content }
=> 32
git cat-file
を使用してオブジェクトのコンテンツを確認してみましょう。
---
$ git cat-file -p bd9dbf5aae1a3862dd1526723246b20206e5fc37
what is up, doc?
---
これで終わりです。有効なGit blobオブジェクトを作成しました。
すべてのGitオブジェクトは同じ方法で保存されますが、タイプが異なるだけです。文字列blobの代わりに、ヘッダーはcommitまたはtreeで始まります。また、blobコンテンツはほぼ何でも構いませんが、commitコンテンツとtreeコンテンツは非常に具体的にフォーマットされています。