English ▾ トピック ▾ 最新バージョン ▾ gitcore-tutorialは2.43.1で最終更新されました

名前

gitcore-tutorial - 開発者向けGitコアチュートリアル

概要

git *

説明

このチュートリアルでは、"コア" Gitコマンドを使用してGitリポジトリをセットアップし、操作する方法を説明します。

Gitをリビジョン管理システムとしてのみ使用する必要がある場合は、"Gitのチュートリアル入門" (gittutorial[7]) または Gitユーザーマニュアルから始めることをお勧めします。

しかし、これらの低レベルツールの理解は、Gitの内部を理解したい場合に役立ちます。

コアGitは「配管 (plumbing)」と呼ばれることが多く、その上のきれいなユーザーインターフェースは「磁器 (porcelain)」と呼ばれます。配管を直接使用することはあまりないかもしれませんが、磁器が流れていないときに配管が何をしているかを知っておくと良いでしょう。

このドキュメントが最初に書かれた頃は、多くのポーセレンコマンドはシェルスクリプトでした。簡潔にするため、ここでは、ポーセレンコマンドを形成するために配管がどのように組み合わされているかを示す例として、それらを使用しています。ソースツリーには、これらのスクリプトの一部が `contrib/examples/` に参考として含まれています。これらはもはやシェルスクリプトとして実装されていませんが、配管レイヤーコマンドが何をするかの説明は依然として有効です。

より深い技術的な詳細は「注記」としてマークされており、最初の読書ではスキップできます。

Gitリポジトリの作成

新しいGitリポジトリの作成はこれ以上簡単ではありません。すべてのGitリポジトリは空の状態から始まり、作業ツリーとして使用したいサブディレクトリを見つけるだけです。これは、全く新しいプロジェクトのための空のサブディレクトリでも、Gitにインポートしたい既存の作業ツリーでも構いません。

最初の例として、既存のファイルがない状態で全く新しいリポジトリをゼロから作成し、それを git-tutorial と呼びます。まず、そのサブディレクトリを作成し、そのサブディレクトリに移動して、git init でGitインフラストラクチャを初期化します。

$ mkdir git-tutorial
$ cd git-tutorial
$ git init

するとGitは次のように応答します。

Initialized empty Git repository in .git/

これは、特に変わったことをしていないことをGitが伝えているだけであり、新しいプロジェクトのためにローカルの .git ディレクトリがセットアップされたことを意味します。これで .git ディレクトリが作成され、ls で中身を確認できます。新しい空のプロジェクトの場合、とりわけ3つのエントリーが表示されるはずです。

  • HEAD というファイルには ref: refs/heads/master が含まれています。これはシンボリックリンクに似ており、HEAD ファイルからの相対パスで refs/heads/master を指しています。

    HEAD リンクが指すファイルがまだ存在しないという事実は気にしないでください。まだ HEAD 開発ブランチを開始するコミットを作成していません。

  • objects というサブディレクトリには、プロジェクトのすべてのオブジェクトが格納されます。オブジェクトを直接見る実質的な理由はないはずですが、これらのオブジェクトがリポジトリ内のすべての実際の データ を含んでいることを知っておくとよいでしょう。

  • refs と呼ばれるサブディレクトリには、オブジェクトへの参照が含まれています。

特に、refs サブディレクトリには、それぞれ headstags という名前の2つのサブディレクトリが含まれています。それらの名前が示す通り、開発の様々な ヘッド (別名 ブランチ) や、リポジトリに作成した特定のバージョンに名前を付けるための タグ への参照が含まれています。

注意点: 特殊な master ヘッドはデフォルトのブランチであり、そのため .git/HEAD ファイルはまだ存在しないにもかかわらずそれを指すように作成されました。基本的に、HEAD リンクは常に現在作業しているブランチを指すことになっており、常に master ブランチで作業することを期待して開始します。

ただし、これは単なる慣例であり、ブランチには好きな名前を付けることができ、master ブランチを 持つ 必要すらありません。ただし、Gitツールの中には .git/HEAD が有効であると仮定するものもあります。

オブジェクト は160ビットのSHA-1ハッシュ(別名 オブジェクト名)で識別され、オブジェクトへの参照は常にそのSHA-1名の40バイトの16進数表現です。refs サブディレクトリ内のファイルには、これらの16進数参照(通常は最後に \n が付く)が含まれていると予想され、したがって、実際にツリーをポピュレートし始めると、これらの refs サブディレクトリにこれらの参照を含む41バイトのファイルが多数表示されると予想すべきです。
上級ユーザーは、このチュートリアルを終えた後、gitrepository-layout[5] を参照することをお勧めします。

これで最初のGitリポジトリが作成されました。もちろん、空の状態ではあまり役に立たないので、データを入れてみましょう。

Gitリポジトリへのデータ投入

シンプルで分かりやすくするために、まずはいくつかの簡単なファイルを投入して、その感覚をつかんでいきましょう。

Gitリポジトリで管理したい任意のファイルをいくつか作成することから始めます。まずはいくつかの悪い例から始めて、これがどのように機能するかを理解していきましょう。

$ echo "Hello World" >hello
$ echo "Silly example" >example

作業ツリー(別名 ワーキングディレクトリ)に2つのファイルを作成しましたが、実際に作業をチェックインするには、2つの手順を踏む必要があります。

  • 作業ツリーの状態に関する情報で インデックス ファイル(別名 キャッシュ)を埋めます。

  • そのインデックスファイルをオブジェクトとしてコミットします。

最初のステップは簡単です。作業ツリーへの変更をGitに伝えたい場合、git update-index プログラムを使用します。このプログラムは通常、更新したいファイル名のリストを受け取りますが、些細な間違いを避けるため、--add フラグ(または --remove フラグで既存のエントリーを削除する)で新しいエントリーを追加することを明示的に指示しない限り、インデックスに新しいエントリーを追加(または既存のエントリーを削除)することを拒否します。

したがって、作成したばかりの2つのファイルをインデックスに投入するには、次のようにします。

$ git update-index --add hello example

これで、Gitにその2つのファイルを追跡するように指示しました。

実際、これを行うと、オブジェクトディレクトリを見ると、Gitがオブジェクトデータベースに2つの新しいオブジェクトを追加したことに気づくでしょう。上記のステップを正確に行った場合、これで次のことができるはずです。

$ ls .git/objects/??/*

2つのファイルが表示されます

.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
.git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962

これらはそれぞれ 557db...f24c7... という名前のオブジェクトに対応しています。

必要に応じて、git cat-file を使用してこれらのオブジェクトを見ることができますが、オブジェクトのファイル名ではなく、オブジェクト名を使用する必要があります。

$ git cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238

ここで、-tgit cat-file にオブジェクトの「タイプ」を伝えるように指示します。Gitは「blob」オブジェクト(つまり、単なる通常のファイル)であることを伝え、その内容を次のように見ることができます。

$ git cat-file blob 557db03

「Hello World」と出力されます。オブジェクト 557db03 は、ファイル hello の内容に過ぎません。

そのオブジェクトとファイル hello 自体を混同しないでください。オブジェクトは文字通り、ファイルの特定の **内容** だけであり、後でファイル hello の内容をどれだけ変更しても、いま見たオブジェクトは決して変わりません。オブジェクトは不変です。
2番目の例は、ほとんどの場合、オブジェクト名を最初の数桁の16進数に短縮できることを示しています。

とにかく、前述したように、通常はオブジェクト自体を実際に見ることはなく、40文字の長い16進数名を入力したいと思うことはありません。上記の脱線は、git update-index が魔法のようなことを行い、実際にファイルのコンテンツをGitオブジェクトデータベースに保存したことを示すためだけにでした。

インデックスの更新は他にも行われました。それは .git/index ファイルを作成しました。これは現在の作業ツリーを記述するインデックスであり、非常に意識すべきものです。繰り返しになりますが、通常はインデックスファイル自体を心配することはありませんが、これまでのところファイルをGitに実際に「チェックイン」していないこと、単にGitにそれらについて **伝えた** だけであることを認識しておく必要があります。

ただし、Gitがそれらを認識しているため、ファイルを操作したり、そのステータスを確認したりするために、最も基本的なGitコマンドの一部を使用できるようになりました。

特に、2つのファイルをまだGitにチェックインせずに、まず hello に別の行を追加することから始めましょう。

$ echo "It's a new day for git" >>hello

以前の hello の状態をGitに伝えたので、git diff-files コマンドを使用して、以前のインデックスと比較してツリーに何が変更されたかをGitに尋ねることができます。

$ git diff-files

おっと。あまり読みやすくありませんでした。それは自身の内部的な diff バージョンを出力しただけで、その内部バージョンは単に「hello」が変更され、以前のオブジェクトの内容が別のものに置き換えられたことを示しているだけです。

読みやすくするために、-p フラグを使って git diff-files にパッチとして差分を出力するよう指示できます。

$ git diff-files -p
diff --git a/hello b/hello
index 557db03..263414f 100644
--- a/hello
+++ b/hello
@@ -1 +1,2 @@
 Hello World
+It's a new day for git

つまり、hello に別の行を追加したことによって生じた変更の差分です。

言い換えれば、git diff-files は常にインデックスに記録されているものと現在の作業ツリーにあるものの違いを示します。これは非常に便利です。

git diff-files -p の一般的な短縮形は、単に git diff と書くことであり、同じことを行います。

$ git diff
diff --git a/hello b/hello
index 557db03..263414f 100644
--- a/hello
+++ b/hello
@@ -1 +1,2 @@
 Hello World
+It's a new day for git

Gitの状態をコミットする

さて、Gitの次の段階に進みましょう。これは、Gitがインデックスで認識しているファイルを、実際のツリーとしてコミットすることです。これを2つの段階で行います。まず、ツリー オブジェクトを作成し、次にその ツリー オブジェクトを、そのツリーが何であるかの説明と、その状態に至るまでの情報とともに、コミット オブジェクトとしてコミットします。

ツリーオブジェクトの作成は簡単で、git write-tree で行われます。オプションやその他の入力はありません。git write-tree は現在のインデックス状態を取得し、そのインデックス全体を記述するオブジェクトを書き込みます。言い換えれば、私たちは現在、すべての異なるファイル名とそのコンテンツ(およびそのパーミッション)を結合し、Gitの「ディレクトリ」オブジェクトに相当するものを作成しています。

$ git write-tree

そして、これにより生成されたツリーの名前が出力されます。この場合(私が説明した通りに実行した場合)、次のようになります。

8988da15d077d4829fc51d8544c097def6644dbb

これはまた別の理解しがたいオブジェクト名です。繰り返しますが、必要であれば、git cat-file -t 8988d... を使用して、今回のオブジェクトが「blob」オブジェクトではなく「tree」オブジェクトであることを確認できます(git cat-file を使用して生のオブジェクト内容を実際に出力することもできますが、ほとんどバイナリの塊なので、あまり面白くありません)。

しかし、通常は git write-tree を単独で使用することはありません。なぜなら、通常は常に git commit-tree コマンドを使用してツリーをコミットオブジェクトにコミットするからです。実際、git write-tree を単独で全く使用せず、その結果を git commit-tree の引数として渡す方が簡単です。

git commit-tree は通常いくつかの引数を取ります。コミットの が何であったかを知りたがりますが、これはこの新しいリポジトリでの最初のコミットであり、親がないため、ツリーのオブジェクト名のみを渡す必要があります。ただし、git commit-tree は標準入力でコミットメッセージも受け取りたがっており、結果のコミットのオブジェクト名を標準出力に書き出します。

そして、ここで HEAD が指す .git/refs/heads/master ファイルを作成します。このファイルはmasterブランチのツリーのトップへの参照を含むことになっており、それがまさに git commit-tree が出力するものなので、一連の簡単なシェルコマンドでこれを行うことができます。

$ tree=$(git write-tree)
$ commit=$(echo 'Initial commit' | git commit-tree $tree)
$ git update-ref HEAD $commit

この場合、これは他の何とも関連のない全く新しいコミットを作成します。通常、これはプロジェクトで一度だけ行われ、それ以降のすべてのコミットは以前のコミットの上に親として追加されます。

繰り返しになりますが、通常はこれを手作業で行うことはありません。git commit という便利なスクリプトがあり、これらすべてを自動的に行ってくれます。したがって、代わりに git commit と書くだけで、上記の魔法のようなスクリプトを自動的に実行してくれます。

変更を行う

hello ファイルに対して git update-index を実行し、その後 hello を変更し、新しい hello の状態をインデックスファイルに保存した状態と比較できたことを覚えていますか?

さらに、git write-tree が **インデックス** ファイルの内容をツリーに書き込むので、私たちがコミットしたのは実際にはファイル hello の **元の** 内容であり、新しい内容ではなかったと私が言ったことを覚えていますか?私たちはそれを意図的に行いました。それは、インデックスの状態と作業ツリーの状態の違い、そしてコミットするときでさえそれらが一致する必要がないことを示すためです。

以前と同様に、git-tutorial プロジェクトで git diff-files -p を実行すると、前回と同じ差分が表示されます。なぜなら、何かをコミットしたことによってインデックスファイルは変更されていないからです。しかし、何かをコミットした今、新しいコマンド git diff-index を使うことを学ぶこともできます。

インデックスファイルとワーキングツリーの差分を表示する git diff-files とは異なり、git diff-index はコミットされた **ツリー** とインデックスファイルまたはワーキングツリーの差分を表示します。言い換えれば、git diff-index は比較対象となるツリーを必要とし、コミットを行う前は比較対象がなかったため、それはできませんでした。

しかし、今ならそれができます。

$ git diff-index -p HEAD

(ここで -pgit diff-files と同じ意味を持ちます)すると、同じ差分が表示されますが、理由は全く異なります。今度はワーキングツリーをインデックスファイルと比較するのではなく、書き込んだばかりのツリーと比較しています。たまたま、それら2つは明らかに同じなので、同じ結果が得られます。

繰り返しますが、これはよくある操作なので、次のように省略することもできます。

$ git diff HEAD

これで、上記の処理が自動的に行われます。

言い換えれば、git diff-index は通常、ツリーを作業ツリーと比較しますが、--cached フラグが指定されると、代わりにインデックスキャッシュの内容のみと比較し、現在の作業ツリーの状態を完全に無視するように指示されます。インデックスファイルをHEADに書き込んだばかりなので、git diff-index --cached -p HEAD を実行すると空の差分セットが返されるはずであり、まさにそうなります。

git diff-index は比較に常にインデックスを使用しており、ツリーを作業ツリーと比較すると言うのは厳密には正確ではありません。特に、比較するファイルのリスト(「メタデータ」)は、--cached フラグが使用されているかどうかにかかわらず、**常に** インデックスファイルから取得されます。--cached フラグは、比較するファイルの **内容** が作業ツリーから取得されるかどうかのみを決定します。

これは理解するのに難しくありません。Gitが明示的に伝えられていないファイルについては何も知らない(または気にしない)と理解すれば、すぐに納得できます。Gitは比較するファイルを **探し** に行くことは決してなく、どのファイルであるかをあなたが伝えることを期待しており、インデックスはそのために存在します。

しかし、次のステップは、私たちが行った **変更** をコミットすることであり、何が起こっているかを理解するために、「作業ツリーの内容」、「インデックスファイル」、および「コミットされたツリー」の違いを覚えておいてください。コミットしたい変更が作業ツリーにあり、常にインデックスファイルを介して作業する必要があるため、最初に行う必要があるのは、インデックスキャッシュを更新することです。

$ git update-index hello

(今回は --add フラグが必要なかったことに注目してください。Gitはすでにそのファイルを知っていたからです)。

ここで、さまざまな git diff-* バージョンに何が起こるかに注目してください。hello をインデックスで更新した後、git diff-files -p は差分を表示しませんが、git diff-index -p HEAD は依然として現在の状態がコミットした状態と異なっていることを **示します**。実際、インデックスが作業ツリーと整合しているため、今や git diff-index--cached フラグを使用するかどうかにかかわらず同じ差分を表示します。

インデックス内の hello を更新したので、新しいバージョンをコミットできます。手作業でツリーを再度書き込み、ツリーをコミットすることもできます(今回は、新しいコミットの **親** がHEADであることをコミットに伝えるために -p HEAD フラグを使用する必要があり、これがもはや初期コミットではないことを示す必要があります)が、それはすでに一度行っているので、今回は便利なスクリプトを使用しましょう。

$ git commit

これにより、コミットメッセージを書き込むためのエディタが起動し、行ったことについて少し説明が表示されます。

好きなメッセージを書き込んでください。_#_ で始まる行はすべて削除され、残りの行が変更のコミットメッセージとして使用されます。この時点でコミットしたくないと判断した場合(編集を続けてインデックスを更新できます)、空のメッセージのままにしてください。そうでなければ、git commit が変更をコミットします。

これで、最初の本格的なGitコミットが完了しました。git commit が実際に何をしているのかに興味があれば、自由に調べてみてください。それは、便利な(?)コミットメッセージヘッダーを生成するための非常にシンプルなシェルスクリプトと、実際のコミット(git commit)を行う数行のコードです。

変更の検査

変更を作成することは便利ですが、後で何が変更されたかを把握できるとさらに便利です。これに最も役立つコマンドは、diff ファミリーの別のコマンド、すなわち git diff-tree です。

git diff-tree に2つの任意のツリーを指定すると、それらの違いが表示されます。しかし、おそらくもっと一般的に、単一のコミットオブジェクトを指定するだけで、そのコミットの親自体を特定し、直接違いを表示できます。したがって、これまで何度も見てきたのと同じ差分を取得するには、次のようにします。

$ git diff-tree -p HEAD

(繰り返しになりますが、-p は人間が読めるパッチとして差分を表示することを意味します)そして、最後のコミット(HEAD 内)が実際に何を変更したかを示します。

Jon Loeligerによる、さまざまな diff-* コマンドがどのように比較を行うかを示すASCIIアートです。

            diff-tree
             +----+
             |    |
             |    |
             V    V
          +-----------+
          | Object DB |
          |  Backing  |
          |   Store   |
          +-----------+
            ^    ^
            |    |
            |    |  diff-index --cached
            |    |
diff-index  |    V
            |  +-----------+
            |  |   Index   |
            |  |  "cache"  |
            |  +-----------+
            |    ^
            |    |
            |    |  diff-files
            |    |
            V    V
          +-----------+
          |  Working  |
          | Directory |
          +-----------+

さらに興味深いことに、git diff-tree--pretty フラグを渡すこともできます。これは、コミットメッセージ、作者、日付も表示するように指示し、一連の差分全体を表示するように指示できます。あるいは、「サイレント」にして、差分を全く表示せず、実際のコミットメッセージのみを表示するように指示することもできます。

実際、git rev-list プログラム(リビジョンのリストを生成する)と組み合わせると、git diff-tree はまさに変更の宝庫となります。git loggit log -p などは、git rev-list の出力を git diff-tree --stdin にパイプする簡単なスクリプトでエミュレートでき、これは初期バージョンの git log が実装されていた方法とまったく同じでした。

バージョンにタグ付けする

Gitには、「軽量 (light)」タグと「注釈付き (annotated)」タグの2種類のタグがあります。

「軽量」タグは技術的には単なるブランチに過ぎず、head と呼ぶ代わりに .git/refs/tags/ サブディレクトリに配置します。したがって、最も単純な形式のタグは、次のものだけで構成されます。

$ git tag my-first-tag

これは現在の HEAD.git/refs/tags/my-first-tag ファイルに書き込むだけで、その後、その特定の状態にこのシンボリック名を使用できます。たとえば、次のようにできます。

$ git diff my-first-tag

現在の状態とそのタグを比較するためにdiffを取ることができます。この時点では明らかに空のdiffになりますが、開発を続けてコミットを続ければ、タグを「アンカーポイント」として使用して、タグ付け以降に何が変更されたかを確認できます。

「注釈付きタグ」は実際のGitオブジェクトであり、タグ付けしたい状態へのポインターだけでなく、短いタグ名とメッセージ、およびオプションで、確かにそのタグを作成したことを示すPGP署名も含まれます。これらの注釈付きタグは、git tag-a または -s フラグを付けて作成します。

$ git tag -s <tagname>

これは現在の HEAD に署名します(ただし、タグ付けするものを指定する別の引数を与えることもできます。たとえば、git tag <tagname> mybranch を使用して現在の mybranch ポイントにタグ付けすることもできます)。

通常、署名付きタグは主要なリリースなどにのみ使用し、軽量タグはどのようなマーキングにも役立ちます。特定のポイントを覚えておきたいと決めたときはいつでも、そのためのプライベートタグを作成するだけで、その時点の状態の素敵なシンボリック名を持つことができます。

リポジトリのコピー

Gitリポジトリは通常、完全に自己完結型で再配置可能です。たとえばCVSとは異なり、「リポジトリ」と「作業ツリー」という概念は別々ではありません。Gitリポジトリは通常、ローカルのGit情報が .git サブディレクトリに隠された作業ツリー **そのもの** です。他に何もありません。見たままが手に入ります。

GitにGit内部情報を追跡しているディレクトリから分離するように指示することはできますが、今はそれを無視します。それは通常のプロジェクトが機能する方法ではなく、特別な用途にのみ意図されています。したがって、「Git情報は常に、それを記述する作業ツリーに直接関連付けられている」というメンタルモデルは、技術的に100%正確ではないかもしれませんが、すべての通常の用途には良いモデルです。

これには2つの意味があります。

  • 作成したチュートリアルリポジトリに飽きた場合(または間違いを犯して最初からやり直したい場合)は、次のように簡単に行うことができます。

    $ rm -rf git-tutorial

    これでなくなります。外部リポジトリはなく、作成したプロジェクト以外の履歴もありません。

  • Gitリポジトリを移動または複製したい場合は、そうすることができます。git clone コマンドがありますが、リポジトリのコピー(完全な履歴を含む)を作成したいだけであれば、通常の cp -a git-tutorial new-git-tutorial で行うことができます。

    Gitリポジトリを移動またはコピーした場合、Gitインデックスファイル(さまざまな情報、特に含まれるファイルの「stat」情報の一部をキャッシュする)は更新が必要になる可能性があります。したがって、cp -a を実行して新しいコピーを作成した後、次のことを行う必要があります。

    $ git update-index --refresh

    新しいリポジトリで、インデックスファイルが最新であることを確認します。

2番目の点は、マシン間でも当てはまることに注意してください。リモートのGitリポジトリは、scprsyncwget など、**あらゆる** 通常のコピーメカニズムで複製できます。

リモートリポジトリをコピーする場合、少なくともインデックスキャッシュを更新する必要があります。特に他人のリポジトリをコピーする場合は、インデックスキャッシュが既知の状態にあることを確認したいことがよくあります(彼らが **何** を行い、まだチェックインしていないかを知りません)。そのため、通常は git update-index の前に次のようにします。

$ git read-tree --reset HEAD
$ git update-index --refresh

これにより、HEAD が指すツリーからインデックスが完全に再構築されます。インデックスの内容は HEAD にリセットされ、その後 git update-index はすべてのインデックスエントリーがチェックアウトされたファイルと一致することを確認します。元のリポジトリの作業ツリーにコミットされていない変更があった場合、git update-index --refresh はそれらを検出し、更新が必要であることを通知します。

上記は、単に次のように書くこともできます。

$ git reset

実際、多くの一般的なGitコマンドの組み合わせは、git xyz インターフェースでスクリプト化できます。さまざまなGitスクリプトが何をしているかを見るだけで学ぶことができます。たとえば、git reset は以前は git reset で実装された上記の2行でしたが、git statusgit commit などは、基本的なGitコマンドを巡る少し複雑なスクリプトです。

多くの(ほとんどの?)公開リモートリポジトリには、チェックアウトされたファイルやインデックスファイルすら含まれておらず、実際のコアGitファイル **のみ** が含まれています。そのようなリポジトリには、通常 .git サブディレクトリすらなく、すべてのGitファイルがリポジトリ内に直接あります。

そのような「生」のGitリポジトリの独自のローカルなライブコピーを作成するには、まずプロジェクト用の独自のサブディレクトリを作成し、次に生のレポジトリの内容を .git ディレクトリにコピーします。たとえば、Gitリポジトリの独自のコピーを作成するには、次のようにします。

$ mkdir my-git
$ cd my-git
$ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git

続いて

$ git read-tree HEAD

インデックスをポピュレートします。しかし、これでインデックスをポピュレートし、すべてのGit内部ファイルを持っていることになりますが、実際に作業する作業ツリーファイルがないことに気づくでしょう。それらを取得するには、次のようにチェックアウトします。

$ git checkout-index -u -a

ここで、-u フラグはチェックアウト後にインデックスを最新の状態に保つことを意味し(後でリフレッシュする必要がないようにするため)、-a フラグは「すべてのファイルをチェックアウトする」ことを意味します(古いコピーやチェックアウトされたツリーの古いバージョンがある場合は、git checkout-index に古いファイルを **強制的に** 上書きするように指示するために、最初に -f フラグを追加する必要がある場合もあります)。

繰り返しますが、これはすべて次のように簡略化できます。

$ git clone git://git.kernel.org/pub/scm/git/git.git/ my-git
$ cd my-git
$ git checkout

これにより、上記のすべてが自動的に行われます。

これで、他者(私)のリモートリポジトリを正常にコピーし、チェックアウトしました。

新しいブランチを作成する

Gitのブランチは、実際には .git/refs/ サブディレクトリ内のGitオブジェクトデータベースへのポインタに過ぎず、すでに説明したように、HEAD ブランチはこれらのオブジェクトポインタの1つへのシンボリックリンクに過ぎません。

プロジェクト履歴内の任意のポイントを選択し、そのオブジェクトのSHA-1名を .git/refs/heads/ 内のファイルに書き込むだけで、いつでも新しいブランチを作成できます。好きなファイル名(および実際にはサブディレクトリ)を使用できますが、慣例では「通常の」ブランチは master と呼ばれます。ただし、これは単なる慣例であり、強制されるものではありません。

例としてそれを示すために、以前使用したgit-tutorialリポジトリに戻り、そこにブランチを作成しましょう。新しいブランチをチェックアウトしたいと言うだけでそれを行います。

$ git switch -c mybranch

現在の HEAD の位置に基づいて新しいブランチを作成し、それに切り替えます。

現在の HEAD 以外の履歴のどこかの時点から新しいブランチを開始することに決めた場合、git switch にチェックアウトの基盤となるものを伝えるだけでそうすることができます。言い換えれば、以前のタグまたはブランチがある場合、次のようにするだけです。

$ git switch -c mybranch earlier-commit

これにより、以前のコミットで新しいブランチ mybranch が作成され、その時点の状態がチェックアウトされます。

いつでも元の master ブランチに、次のようにして戻ることができます。

$ git switch master

(または他のブランチ名でも構いません)そして、現在どのブランチにいるのか忘れてしまった場合は、簡単な

$ cat .git/HEAD

それはどこを指しているのか教えてくれます。持っているブランチのリストを取得するには、次のように言えます。

$ git branch

これは以前は ls .git/refs/heads を囲むだけの単純なスクリプトでした。現在いるブランチの前にアスタリスクが表示されます。

新しいブランチを実際にチェックアウトして切り替える **ことなく** 作成したい場合があります。その場合は、次のコマンドを使用します。

$ git branch <branchname> [startingpoint]

これは単にブランチを _作成_ するだけで、それ以上のことはしません。その後、実際にそのブランチで開発したいと決めたら、引数としてブランチ名を指定して通常の _git switch_ でそのブランチに切り替えることができます。

2つのブランチをマージする

ブランチを持つ考え方の一つは、その中で(おそらく実験的な)作業を行い、最終的にメインブランチにマージすることです。したがって、元の master ブランチと同じであった上記の mybranch を作成したと仮定して、そのブランチにいることを確認し、そこで作業を行いましょう。

$ git switch mybranch
$ echo "Work, work, work" >>hello
$ git commit -m "Some work." -i hello

ここでは、hello に別の行を追加し、git update-index hellogit commit の両方を省略形で実行しました。これは、git commit にファイル名を直接与え、-i フラグを付けたものです(これは、コミットを作成する際に、これまでにインデックスファイルに対して行ったことに加えて、そのファイルを Git に _含める_ ように指示します)。-m フラグは、コマンドラインからコミットログメッセージを与えるためのものです。

さて、もう少し面白くするために、他の誰かが元のブランチで作業し、マスターブランチに戻り、そこで同じファイルを別の方法で編集することでそれをシミュレートしてみましょう。

$ git switch master

ここで、hello の内容を見て、mybranch で行った作業が含まれていないことに注目してください。なぜなら、その作業は master ブランチでは全く行われていないからです。それから、次のようにします。

$ echo "Play, play, play" >>hello
$ echo "Lots of fun" >>example
$ git commit -m "Some fun." -i hello example

マスターブランチは明らかに気分が良いので。

これで2つのブランチがあり、行われた作業をマージすることにしました。その前に、何が起こっているかを表示するのに役立つクールなグラフィカルツールを紹介しましょう。

$ gitk --all

これは両方のブランチ(--all は通常、現在の HEAD のみを表示することを意味します)とその履歴をグラフィカルに表示します。また、共通のソースからどのようにしてそれらが作成されたかも正確に確認できます。

とにかく、_gitk_ を終了し(^Q またはファイルメニュー)、mybranch ブランチで行った作業を master ブランチ(これも現在私たちの HEAD です)にマージすることに決めましょう。そのためには、git merge という便利なスクリプトがあり、どのブランチを解決し、マージが何であるかを知りたがっています。

$ git merge -m "Merge work in mybranch" mybranch

最初の引数は、マージが自動的に解決できる場合のコミットメッセージとして使用されます。

しかし、今回は意図的にマージを手動で修正する必要がある状況を作成したので、Gitは可能な限り自動的に処理し(この場合は mybranch に差分がなかった example ファイルをマージするだけです)、次のように言います。

	Auto-merging hello
	CONFLICT (content): Merge conflict in hello
	Automatic merge failed; fix conflicts and then commit the result.

「自動マージ」を行い、hello での競合により失敗したと伝えています。

心配無用です。hello に残された(些細な)競合は、CVSを使ったことがある人ならすでにお馴染みの形式なので、エディタ(何であれ)で hello を開き、どうにかして修正しましょう。私が提案するのは、hello がすべての4行を含むようにすることです。

Hello World
It's a new day for git
Play, play, play
Work, work, work

手動マージに満足したら、次のコマンドを実行するだけです。

$ git commit -i hello

これは、マージをコミットしていることを非常に大きな音で警告しますが(正しいので気にしないでください)、git merge の冒険について短いマージメッセージを書くことができます。

終了したら、gitk --all を起動して、履歴がどのように見えるかをグラフィカルに確認してください。mybranch はまだ存在し、必要であればそれに切り替えて作業を続けることができることに注目してください。mybranch ブランチにはマージは含まれませんが、次回 master ブランチからマージするときには、Gitはどのようにマージしたかを知っているので、そのマージを再度行う必要はありません。

特にX-Window環境で常に作業するわけではない場合に便利なもう一つのツールは、git show-branch です。

$ git show-branch --topo-order --more=1 master mybranch
* [master] Merge work in mybranch
 ! [mybranch] Some work.
--
-  [master] Merge work in mybranch
*+ [mybranch] Some work.
*  [master^] Some fun.

最初の2行は、ツリーのトップにあるコミットのタイトルを持つ2つのブランチを表示していることを示しています。あなたは現在 master ブランチにいます(アスタリスク * 文字に注目してください)。後続の出力行の最初の列は master ブランチに含まれるコミットを表示するために使用され、2番目の列は mybranch ブランチのために使用されます。3つのコミットがタイトルとともに表示されています。これらすべては最初の列に空白ではない文字(* は現在のブランチ上の通常のコミットを示し、- はマージコミットです)があり、これは現在 master ブランチの一部であることを意味します。「Some work」コミットのみが2番目の列にプラス + 文字を持っています。なぜなら mybranch はマスターブランチからのこれらのコミットを組み込むためにマージされていないからです。コミットログメッセージの前の括弧内の文字列は、コミットに名前を付けるために使用できる短い名前です。上記の例では、_master_ と _mybranch_ はブランチヘッドです。_master^_ は _master_ ブランチヘッドの最初の親です。より複雑なケースを見たい場合は、gitrevisions[7] を参照してください。

_--more=1_ オプションがない場合、_git show-branch_ は _[master^]_ コミットを出力しません。なぜなら、_[mybranch]_ コミットは _master_ と _mybranch_ の両方の先端の共通の祖先だからです。詳細については、git-show-branch[1] を参照してください。
マージ後に master ブランチにさらにコミットがあった場合、デフォルトではマージコミット自体は git show-branch によって表示されません。この場合、マージコミットを表示させるには --sparse オプションを指定する必要があります。

さて、あなたが mybranch で全ての作業を行い、その努力の成果がついに master ブランチにマージされたと仮定しましょう。mybranch に戻り、git merge を実行して「アップストリームの変更」を自分のブランチに取り込みます。

$ git switch mybranch
$ git merge -m "Merge upstream changes." master

これは次のような出力を生成します(実際のコミットオブジェクト名は異なるでしょう)。

Updating from ae3a2da... to a80b4aa....
Fast-forward (no commit created; -m option ignored)
 example | 1 +
 hello   | 1 +
 2 files changed, 2 insertions(+)

あなたのブランチにはすでに master ブランチにマージされていたもの以上のものが含まれていなかったため、マージ操作は実際にはマージを実行しませんでした。代わりに、単にブランチのツリーのトップを master ブランチのそれと同じに更新しただけです。これはしばしば fast-forward マージと呼ばれます。

gitk --all を再度実行してコミットの祖先がどのように見えるかを確認するか、show-branch を実行してこれを知らせることができます。

$ git show-branch master mybranch
! [master] Merge work in mybranch
 * [mybranch] Merge work in mybranch
--
-- [master] Merge work in mybranch

外部作業のマージ

自分のブランチとマージするよりも、他の誰かとマージする方がはるかに一般的です。そのため、Gitがそれを非常に簡単に行うことも指摘する価値があります。実際、git merge を行うのとそれほど違いはありません。実際、リモートマージは、「リモートリポジトリから一時タグに作業をフェッチする」と、それに続く git merge に過ぎません。

リモートリポジトリからのフェッチは、当然のことながら git fetch で行われます。

$ git fetch <remote-repository>

以下のトランスポートのいずれかを使用して、ダウンロード元のリポジトリの名前を指定できます。

SSH

remote.machine:/path/to/repo.git/ または

ssh://remote.machine/path/to/repo.git/

このトランスポートはアップロードとダウンロードの両方に使用でき、リモートマシンへの ssh 経由でのログイン権限が必要です。これは、両端が持つヘッドコミットを交換することで、もう一方の側に不足しているオブジェクトのセットを特定し、(最小限の)オブジェクトセットを転送します。これは、リポジトリ間でGitオブジェクトを交換する最も効率的な方法です。

ローカルディレクトリ

/path/to/repo.git/

このトランスポートはSSHトランスポートと同じですが、ssh を介してリモートマシンで別の端を実行する代わりに、_sh_ を使用してローカルマシンで両端を実行します。

Gitネイティブ

git://remote.machine/path/to/repo.git/

このトランスポートは匿名ダウンロードのために設計されました。SSHトランスポートと同様に、ダウンストリーム側に不足しているオブジェクトのセットを特定し、(最小限の)オブジェクトセットを転送します。

HTTP(S)

http://remote.machine/path/to/repo.git/

httpおよびhttps URLからのダウンローダーは、最初に repo.git/refs/ ディレクトリ下の指定されたrefnameを見てリモートサイトから最上位のコミットオブジェクト名を取得し、次にそのコミットオブジェクトのオブジェクト名を使用して repo.git/objects/xx/xxx... からダウンロードしてコミットオブジェクトを取得しようとします。次に、コミットオブジェクトを読み取ってその親コミットと関連するツリーオブジェクトを特定し、必要なすべてのオブジェクトを取得するまでこのプロセスを繰り返します。この動作のため、これらは _コミットウォーカー_ とも呼ばれることがあります。

コミットウォーカー は、GitネイティブトランスポートのようなGitを意識したスマートなサーバーを必要としないため、_ダムトランスポート_ とも呼ばれることがあります。ディレクトリインデックスをサポートしない通常のHTTPサーバーで十分です。ただし、ダムトランスポートダウンローダーを支援するために、_git update-server-info_ でリポジトリを準備する必要があります。

リモートリポジトリからフェッチしたら、現在のブランチと merge します。

しかし、fetch してからすぐに merge するのは非常に一般的なことなので、それは git pull と呼ばれており、単に次のようにすることができます。

$ git pull <remote-repository>

そして、オプションでリモートのブランチ名を2番目の引数として指定できます。

ブランチとして持ちたいだけローカルリポジトリを保持し、_git pull_ でそれらの間でマージすることで、ブランチを全く使用しないことも可能です。このアプローチの利点は、各 branch のファイルセットをチェックアウトしたままにできるため、複数の開発ラインを同時に扱っている場合に切り替えが簡単になることです。もちろん、複数の作業ツリーを保持するためにディスク使用量が増えるという代償を払うことになりますが、今日のディスク容量は安価です。

同じリモートリポジトリから時々プルする可能性が高いです。省略形として、リモートリポジトリのURLをローカルリポジトリの設定ファイルに次のように保存できます。

$ git config remote.linus.url https://git.kernel.org/pub/scm/git/git.git/

そして、完全なURLの代わりに _git pull_ で「linus」キーワードを使用します。

  1. git pull linus

  2. git pull linus tag v0.99.1

上記は以下と同等です。

  1. git pull http://www.kernel.org/pub/scm/git/git.git/ HEAD

  2. git pull http://www.kernel.org/pub/scm/git/git.git/ tag v0.99.1

マージはどのように機能するのか?

このチュートリアルでは、ポーセレンが流れないときに配管がどのように役立つかを示すと言いましたが、これまでのところ、マージが実際にどのように機能するかについては説明していませんでした。このチュートリアルを初めて読む場合は、「作業の公開」セクションにスキップして後でここに戻ることをお勧めします。

OK、まだ私と一緒にいますか?例を挙げるために、「hello」と「example」ファイルのある以前のリポジトリに戻り、マージ前の状態に戻しましょう。

$ git show-branch --more=2 master mybranch
! [master] Merge work in mybranch
 * [mybranch] Merge work in mybranch
--
-- [master] Merge work in mybranch
+* [master^2] Some work.
+* [master^] Some fun.

git merge を実行する前は、master ヘッドは「Some fun.」コミットにあり、mybranch ヘッドは「Some work.」コミットにあったことを思い出してください。

$ git switch -C mybranch master^2
$ git switch master
$ git reset --hard master^

巻き戻した後、コミット構造は次のようになるはずです。

$ git show-branch
* [master] Some fun.
 ! [mybranch] Some work.
--
*  [master] Some fun.
 + [mybranch] Some work.
*+ [master^] Initial commit

これで、手動でマージを試す準備ができました。

git merge コマンドは、2つのブランチをマージする際に3-wayマージアルゴリズムを使用します。まず、それらの共通の祖先を見つけます。使用するコマンドは git merge-base です。

$ mb=$(git merge-base HEAD mybranch)

このコマンドは共通の祖先のコミットオブジェクト名を標準出力に書き込むので、次のステップで使用するために出力を変数にキャプチャしました。ちなみに、この場合、共通の祖先コミットは「Initial commit」コミットです。これは次のようにして確認できます。

$ git name-rev --name-only --tags $mb
my-first-tag

共通の祖先コミットを見つけたら、次のステップはこれです。

$ git read-tree -m -u $mb HEAD mybranch

これはすでに見たことのある git read-tree コマンドですが、以前の例とは異なり3つのツリーを取ります。これは各ツリーの内容をインデックスファイル内の異なる _ステージ_ に読み込みます(最初のツリーはステージ1、2番目はステージ2など)。3つのツリーを3つのステージに読み込んだ後、3つのステージすべてで同じパスはステージ0に _折りたたまれます_。また、3つのステージのうち2つで同じパスもステージ0に折りたたまれ、ステージ1と異なる(つまり、片側だけが共通の祖先から変更された)ステージ2またはステージ3のSHA-1を使用します。

_折りたたみ_ 操作の後、3つのツリーで異なるパスは非ゼロのステージに残されます。この時点で、このコマンドでインデックスファイルを検査できます。

$ git ls-files --stage
100644 7f8b141b65fdcee47321e399a2598a235a032422 0	example
100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1	hello
100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2	hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3	hello

たった2つのファイルの例では、変更されていないファイルがなかったので、_example_ だけが折りたたみ結果となりました。しかし、実際の大きなプロジェクトでは、1つのコミットで変更されるファイル数が少ない場合、この _折りたたみ_ によってほとんどのパスがかなり迅速に簡単にマージされ、非ゼロのステージにはごくわずかな実際の変更しか残りません。

非ゼロのステージのみを見るには、--unmerged フラグを使用します。

$ git ls-files --unmerged
100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1	hello
100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2	hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3	hello

マージの次のステップは、3-wayマージを使用して、これらのファイルの3つのバージョンをマージすることです。これは、_git merge-index_ コマンドの引数の一つとして _git merge-one-file_ コマンドを与えることによって行われます。

$ git merge-index git-merge-one-file hello
Auto-merging hello
ERROR: Merge conflict in hello
fatal: merge program failed

git merge-one-file スクリプトは、3つのバージョンを記述するパラメータと共に呼び出され、マージ結果を作業ツリーに残す責任があります。これはかなり簡単なシェルスクリプトであり、最終的にはRCSスイートの merge プログラムを呼び出してファイルレベルの3-wayマージを実行します。この場合、merge は競合を検出し、競合マーク付きのマージ結果は作業ツリーに残されます。これは、この時点で ls-files --stage を再度実行すると確認できます。

$ git ls-files --stage
100644 7f8b141b65fdcee47321e399a2598a235a032422 0	example
100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1	hello
100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2	hello
100644 cc44c73eb783565da5831b4d820c962954019b69 3	hello

これは、_git merge_ が制御をあなたに戻した後、インデックスファイルと作業ファイルの状態であり、解決すべき競合するマージが残されています。パス hello がまだマージされていないこと、そしてこの時点での _git diff_ で見られるのはステージ2(つまりあなたのバージョン)からの差分であることに注意してください。

作業の公開

さて、私たちはリモートリポジトリから他人の作業を使用できますが、**あなた** は他の人がプルできるようにリポジトリをどのように準備できますか?

あなたの主要なリポジトリが .git サブディレクトリとしてぶら下がっている作業ツリーで実際の作業を行います。そのリポジトリをリモートからアクセス可能にし、人々にそこからプルするように依頼することも **できます** が、実際には通常そうはしません。推奨される方法は、公開リポジトリを持ち、それを他の人がアクセスできるようにし、主要な作業ツリーで行った変更が良い状態になったら、それから公開リポジトリを更新することです。これはしばしば _プッシュ_ と呼ばれます。

この公開リポジトリはさらにミラーリングされることがあり、kernel.org のGitリポジトリはそうやって管理されています。

ローカル(プライベート)リポジトリからリモート(パブリック)リポジトリへの変更を公開するには、リモートマシンへの書き込み権限が必要です。SSHアカウントがそこにあり、単一のコマンド git-receive-pack を実行する必要があります。

まず、公開リポジトリを格納するリモートマシン上に空のリポジトリを作成する必要があります。この空のリポジトリは、後でプッシュすることで投入され、最新の状態に保たれます。当然ながら、このリポジトリの作成は一度だけ行えばよいです。

git push は、ローカルマシン上の _git send-pack_ とリモートマシン上の _git-receive-pack_ という2つのコマンドを使用します。2つの間のネットワーク上の通信は、内部的にSSH接続を使用します。

あなたのプライベートリポジトリのGitディレクトリは通常 .git ですが、あなたの公開リポジトリはしばしばプロジェクト名、つまり _<project>_.git という名前になります。プロジェクト my-git のためにそのような公開リポジトリを作成しましょう。リモートマシンにログインした後、空のディレクトリを作成します。

$ mkdir my-git.git

次に、git init を実行してそのディレクトリをGitリポジトリにしますが、今回はその名前が通常の .git ではないので、少し異なる方法で行います。

$ GIT_DIR=my-git.git git init

このディレクトリが、選択した転送方法で変更をプルしたい他のユーザーに利用可能であることを確認してください。また、$PATHgit-receive-pack プログラムがあることも確認する必要があります。

sshdの多くのインストールでは、プログラムを直接実行してもログインシェルとしてシェルが呼び出されません。これは、ログインシェルが _bash_ の場合、.bash_profile ではなく .bashrc のみが読み込まれることを意味します。回避策として、.bashrc が _git-receive-pack_ プログラムを実行できるように $PATH を設定していることを確認してください。
このリポジトリをHTTP経由でアクセスできるように公開する予定がある場合は、この時点で mv my-git.git/hooks/post-update.sample my-git.git/hooks/post-update を実行する必要があります。これにより、このリポジトリにプッシュするたびに git update-server-info が実行されるようになります。

これで「公開リポジトリ」は変更を受け入れる準備ができました。プライベートリポジトリがあるマシンに戻り、このコマンドを実行してください。

$ git push <public-host>:/path/to/my-git.git master

これは、あなたの公開リポジトリを、あなたの現在のリポジトリ内の指定されたブランチヘッド(この場合は master)およびそこから到達可能なオブジェクトと一致させるように同期します。

実際の例として、これは私が自分の公開Gitリポジトリを更新する方法です。Kernel.orgミラーネットワークは、他の公開されているマシンへの伝播を処理します。

$ git push master.kernel.org:/pub/scm/git/git.git/

リポジトリのパック化

以前、作成するGitオブジェクトごとに .git/objects/??/ ディレクトリの下に1つのファイルが保存されることを確認しました。この表現は、アトミックかつ安全に作成するには効率的ですが、ネットワークを介して転送するにはあまり便利ではありません。Gitオブジェクトは作成されると不変であるため、「それらをまとめてパックする」ことでストレージを最適化する方法があります。次のコマンドを実行してください。

$ git repack

それがあなたのためにやってくれます。チュートリアルの例に従った場合、今までに .git/objects/??/ ディレクトリに約17個のオブジェクトが蓄積されているはずです。git repack はパックされたオブジェクトの数を表示し、パックされたファイルを .git/objects/pack ディレクトリに保存します。

.git/objects/pack ディレクトリには、pack-*.packpack-*.idx の2つのファイルが表示されます。これらは密接に関連しており、何らかの理由で手動で別のリポジトリにコピーする場合は、必ず一緒にコピーするようにしてください。前者はパック内のオブジェクトのすべてのデータを保持し、後者はランダムアクセス用のインデックスを保持します。

心配な場合は、_git verify-pack_ コマンドを実行すると、破損したパックがあるかどうかを検出できますが、あまり心配する必要はありません。私たちのプログラムは常に完璧です ;-)。

オブジェクトをパックしたら、パックファイルに含まれるアンパックされたオブジェクトを残しておく必要はもうありません。

$ git prune-packed

それらを削除してくれます。

興味があれば、git prune-packed を実行する前後で find .git/objects -type f を実行してみてください。また、git count-objects は、リポジトリ内のアンパックされたオブジェクトの数とそれらが消費しているスペースを教えてくれます。

git pull はHTTPトランスポートの場合、比較的少ないオブジェクトが比較的大きなパックに含まれている可能性があるため、少し手間がかかります。公開リポジトリからのHTTPプルが多いと予想される場合は、頻繁に再パック&プルーニングを行うか、全く行わない方がよいでしょう。

この時点で git repack を再度実行すると、「Nothing new to pack.」と表示されます。開発を続け、変更が蓄積された後、再度 git repack を実行すると、前回リポジトリをパックしてから作成されたオブジェクトを含む新しいパックが作成されます。最初のインポート後すぐにプロジェクトをパックすることをお勧めします(ゼロからプロジェクトを開始する場合を除く)。その後は、プロジェクトの活動状況に応じて、時々 git repack を実行することをお勧めします。

リポジトリが git pushgit pull を介して同期されるとき、ソースリポジトリにパックされたオブジェクトは通常、宛先ではアンパックされた状態で保存されます。これにより、両端で異なるパッキング戦略を使用できますが、同時に、両方のリポジトリを時々再パックする必要があることを意味します。

他の人と協力する

Gitは真に分散型のシステムですが、開発者の非公式な階層でプロジェクトを組織すると便利なことがよくあります。Linuxカーネルの開発はこのように行われています。Randy Dunlapのプレゼンテーションには、その見事な図解(17ページ、「Merges to Mainline」)があります。

この階層が純粋に **非公式** であることを強調する必要があります。この階層が示唆する「パッチの流れの連鎖」を強制するGitの基本的なものは何もありません。1つのリモートリポジトリからのみプルする必要はありません。

「プロジェクトリーダー」に推奨されるワークフローは次のとおりです。

  1. ローカルマシンにプライマリリポジトリを準備します。作業はそこで行われます。

  2. 他者がアクセスできる公開リポジトリを準備します。

    他の人がダムトランスポートプロトコル(HTTP)経由でリポジトリからプルしている場合、このリポジトリを _ダムトランスポートフレンドリー_ に保つ必要があります。git init の後、標準テンプレートからコピーされた $GIT_DIR/hooks/post-update.sample には _git update-server-info_ への呼び出しが含まれていますが、mv post-update.sample post-update でフックを手動で有効にする必要があります。これにより、_git update-server-info_ が必要なファイルを最新の状態に保つことができます。

  3. プライマリリポジトリから公開リポジトリへプッシュします。

  4. 公開リポジトリを _git repack_ します。これにより、初期のオブジェクトセットをベースラインとして含む大きなパックが作成され、リポジトリからプルするために使用されるトランスポートがパックされたリポジトリをサポートしている場合は、_git prune_ も行われます。

  5. プライマリリポジトリで作業を続けます。変更には、自身の修正、電子メールで受け取ったパッチ、「サブシステムメンテナー」の「公開」リポジトリをプルすることによるマージ結果が含まれます。

    このプライベートリポジトリは、好きなときにいつでも再パックできます。

  6. 変更を公開リポジトリにプッシュし、それを一般に発表します。

  7. 時々、公開リポジトリを _git repack_ します。ステップ5に戻り、作業を続行します。

そのプロジェクトで作業し、独自の「公開リポジトリ」を持つ「サブシステムメンテナー」に推奨される作業サイクルは次のとおりです。

  1. 「プロジェクトリード」の公開リポジトリで _git clone_ を実行して、作業リポジトリを準備します。最初のクローン作成に使用されるURLは、remote.origin.url 設定変数に格納されます。

  2. 「プロジェクトリーダー」がするのと同じように、他の人がアクセスできる公開リポジトリを用意します。

  3. 「プロジェクトリーダー」の公開リポジトリから、自分の公開リポジトリにパックされたファイルをコピーします。ただし、「プロジェクトリーダー」のリポジトリが自分のマシンと同じマシンにある場合は除きます。後者の場合、objects/info/alternates ファイルを使って、借用元リポジトリを指定できます。

  4. プライマリリポジトリから公開リポジトリにプッシュします。git repack を実行し、リポジトリからプルするために使用されるトランスポートがパックされたリポジトリをサポートしている場合は、git prune も実行します。

  5. プライマリリポジトリで作業を続けます。変更には、自分自身の修正、メールで受け取ったパッチ、そして「プロジェクトリーダー」や場合によっては「サブサブシステムメンテナー」の「公開」リポジトリをプルすることによるマージが含まれます。

    このプライベートリポジトリは、好きなときにいつでも再パックできます。

  6. 自分の公開リポジトリに変更をプッシュし、「プロジェクトリーダー」や場合によっては「サブサブシステムメンテナー」にそこからプルするように依頼します。

  7. 時々、公開リポジトリを _git repack_ します。ステップ5に戻り、作業を続行します。

「公開」リポジトリを持たない「個人開発者」の推奨される作業サイクルは少し異なります。それは次のようになります。

  1. 「プロジェクトリーダー」(またはサブシステムに取り組んでいる場合は「サブシステムメンテナー」)の公開リポジトリを git clone して、自分の作業リポジトリを準備します。最初のクローンに使用されたURLは、remote.origin.url 設定変数に保存されます。

  2. 自分のリポジトリの master ブランチで作業を行います。

  3. 定期的に、アップストリームの公開リポジトリから git fetch origin を実行します。これは git pull の前半のみを行い、マージはしません。公開リポジトリのヘッドは .git/refs/remotes/origin/master に保存されます。

  4. git cherry origin を使用して、自分のパッチのうちどのパッチが受け入れられたかを確認し、および/または git rebase origin を使用して、未マージの変更を更新されたアップストリームに転送します。

  5. git format-patch origin を使用して、アップストリームへのメール提出用のパッチを準備し、送信します。ステップ2に戻り、作業を続行します。

他のユーザーとの共同作業、共有リポジトリスタイル

CVSを経験している場合、前述のセクションで提案された協力スタイルは新しいかもしれません。心配する必要はありません。Gitは、おそらくより慣れている「共有公開リポジトリ」スタイルの協力もサポートしています。

詳細については、gitcvs-migration[7] を参照してください。

作業をまとめる

同時に複数の作業に取り組む可能性が高いでしょう。Gitのブランチを使用すると、これら多かれ少なかれ独立したタスクを簡単に管理できます。

以前、「楽しいことと仕事」の例で2つのブランチを使って、ブランチがどのように機能するかを見てきました。ブランチが2つ以上ある場合も考え方は同じです。仮に「master」ヘッドから開始し、「master」ブランチにいくつかの新しいコードがあり、「commit-fix」と「diff-fix」ブランチに2つの独立した修正があるとします。

$ git show-branch
! [commit-fix] Fix commit message normalization.
 ! [diff-fix] Fix rename detection.
  * [master] Release candidate #1
---
 +  [diff-fix] Fix rename detection.
 +  [diff-fix~1] Better common substring algorithm.
+   [commit-fix] Fix commit message normalization.
  * [master] Release candidate #1
++* [diff-fix~2] Pretty-print messages.

両方の修正は十分にテストされており、この時点で両方をマージしたいとします。まず diff-fix をマージし、次に commit-fix をマージすることができます。このようにします。

$ git merge -m "Merge fix in diff-fix" diff-fix
$ git merge -m "Merge fix in commit-fix" commit-fix

その結果は次のようになります。

$ git show-branch
! [commit-fix] Fix commit message normalization.
 ! [diff-fix] Fix rename detection.
  * [master] Merge fix in commit-fix
---
  - [master] Merge fix in commit-fix
+ * [commit-fix] Fix commit message normalization.
  - [master~1] Merge fix in diff-fix
 +* [diff-fix] Fix rename detection.
 +* [diff-fix~1] Better common substring algorithm.
  * [master~2] Release candidate #1
++* [master~3] Pretty-print messages.

しかし、本当に独立した変更のセットを持っている場合(順序が重要である場合、定義上それらは独立していません)、一方のブランチを先にマージし、もう一方を次にマージする特別な理由はありません。代わりに、これら2つのブランチを現在のブランチに一度にマージできます。まず、先ほど行ったことを元に戻し、やり直しましょう。これら2つのマージの前のマスターブランチの状態に戻すには、master~2 にリセットします。

$ git reset --hard master~2

git show-branch が、実行した2つの git merge の前の状態と一致することを確認できます。次に、続けて2つの git merge コマンドを実行する代わりに、これら2つのブランチヘッドをマージします(これは making an Octopus と呼ばれます)。

$ git merge commit-fix diff-fix
$ git show-branch
! [commit-fix] Fix commit message normalization.
 ! [diff-fix] Fix rename detection.
  * [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
---
  - [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
+ * [commit-fix] Fix commit message normalization.
 +* [diff-fix] Fix rename detection.
 +* [diff-fix~1] Better common substring algorithm.
  * [master~1] Release candidate #1
++* [master~2] Pretty-print messages.

タコ(Octopus)ができるからといって、タコをしてはいけないことに注意してください。タコは有効な作業であり、同時に2つ以上の独立した変更をマージする場合、コミット履歴をより簡単に確認できることがよくあります。ただし、マージしようとしているブランチのいずれかでマージ競合が発生し、手動で解決する必要がある場合、それはそれらのブランチで行われた開発が結局独立していなかったことを示しており、一度に2つずつマージし、競合をどのように解決したか、そして一方の変更を他方よりも優先した理由を文書化する必要があります。そうしないと、プロジェクトの履歴を追跡することが難しくなり、容易にはなりません。

GIT

git[1]スイートの一部

scroll-to-top