SYNOPSIS
git *
DESCRIPTION
このチュートリアルでは、「コア Git コマンド」(core Git commands)を使用して Git リポジトリーを設定および操作する方法について説明します。
あなたが単に Git をバージョン管理システムとして使用したい場合は、 まず 「A Tutorial Introduction to Git」(gittutorial(7))または the Git User Manual から始めることをお勧めします。
しかしながら、 Git の内部を理解したい場合は、 これら低レベル・ツールへの理解が役に立つことがあります。
コアGit(core Git)は「配管」(plumbing)と呼ばれることが多く、その上に「磁器」(porcelain)と呼ばれるより美しいユーザーインターフェイスがあります。配管コマンドを直接使用することはあまりありませんが、磁器コマンドが使えない時に配管コマンドでどうするかを知っておくのは良い事です。
このドキュメントが最初に作成されたとき、多くの磁器コマンドはシェルスクリプトでした。説明を簡単にするために、配管がどのように組み合わされて磁器コマンドを形成するかを示す例としていまだそれらを使用しています。ソースツリーには、参照用に contrib/examples/ にこれらのスクリプトの一部が含まれています。これらは最早シェルスクリプトとして実装されなくなりましたが、それでも、配管レイヤーコマンドの機能の説明は引き続き有効です。
|
Note
|
より深い技術的な詳細は、多くの場合 Note 欄になっています。 最初に読むときはスキップしてかまいません。 |
Creating a Git repository
新しい Git リポジトリーの作成はこれ以上ないほど簡単です。 すべての Git リポジトリーは空(empty)から始まります。 必要なのは、 作業ツリーとして使用するサブディレクトリを用意することだけです。 まったく新しいプロジェクトの場合は、 それは空のものか、 あるいは、 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 サブディレクトリには、それぞれ heads と tags という名前の2つのサブディレクトリが含まれます。それらは、名前が示すとおりに機能します。つまり、開発のさまざまな「ヘッド」(先頭)(別名「ブランチ」)への参照と、リポジトリ内の特定のバージョンに名前を付けるために作成した「タグ」への参照が含まれます。
注: 特別な master ヘッドがデフォルトのブランチであるため、作成された .git/HEAD ファイルは、まだ存在していなくてもそれを指します。基本的に、 HEAD リンクは常に現在作業しているブランチを指しているはずであり、いつも master ブランチでの作業から始まることを期待します。
けれども、これは単なる慣例であり、ブランチには任意の名前を付けることができ、あなたは「master」ブランチを持つ必要はありません。ただし、多くのGitツールは .git/HEAD が最初から有効であると想定します。
|
Note
|
「オブジェクト」は、 その 160ビットSHA-1ハッシュ、 つまり「オブジェクト名」によって識別されます。 オブジェクトへの参照は、 常にそのSHA-1名の16進数表現の40バイトです。 refs サブディレクトリ内のファイルには、 このような16進数表現の参照が含まれていることが期待されており(通常は末尾に \n が付く)、 ツリーに実際にデータを追加し始めると、 これらの refs サブディレクトリ内に、 そのような参照を含む41バイト(訳注:SHA-1名の16進数表現の40バイト + \n )のファイルが多数存在するようになることが予想されます。 |
|
Note
|
上級ユーザーは、このチュートリアルを終了した後、 gitrepository-layout(5) を確認することをお勧めします。 |
これで、あなたの最初のGitリポジトリが作成されました。もちろん、空なのであまり役に立ちません。なので、データの入力を始めましょう。
Populating a Git repository
我々はシンプルかつ愚直に行きたいと思います、まずは簡単なファイルをいくつか入力して、その感触をつかむことから始めます。
あなたのGitリポジトリに保持したいランダムファイルを作成することから始めます。これがどのように機能するかを理解するために、いくつかの悪い例から始めます:
$ echo "Hello World" >hello
$ echo "Silly example" >example
これで、あなたの作業ツリー(working tree)(別名「作業ディレクトリ」(working directory))に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
ここで、 -t は git cat-file に、オブジェクトの「タイプ」が何であるかを通知させます。 Gitは、「blob」(ブロブ)オブジェクト(つまり、通常のファイル)があることを通知し、あなたは以下のコマンドで内容を表示できます
$ git cat-file blob 557db03
"Hello World" が出力されます。オブジェクト 557db03 は、ファイル hello の内容そのものです。
|
Note
|
そのオブジェクトをファイル hello 自体と混同しないでください。オブジェクトは文字通りファイルの特定の「内容」(contents)であり、後でファイル hello の内容を変更しても、先ほど見たオブジェクトは変更されません。オブジェクトは不変(immutable)です。 |
|
Note
|
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
Gitには以前の hello の状態について伝えてあるので、 これで、 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 に別の行を追加することによって引き起された変更の diff です。
言い換えると、 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
Committing Git state
次に、私達はGitの次の段階に進みます。これは、Gitが知っているファイルをインデックスより取得し、それらを実際のツリーとしてコミットすることです。これは次の2つのフェーズで行います。「ツリー」オブジェクトの作成し、そして、ツリーが何であるかについての説明と、私たちがどのようにしてその状態に到達したかについての情報とともに、その「ツリー」オブジェクトを「コミット」オブジェクトとしてコミットします。
ツリーオブジェクトの作成は至極簡単で、 git write-tree を使用します。オプションやその他の入力はありません。 git write-tree は現在のインデックスの状態を取得し、そのインデックス全体を記述するオブジェクトを書き込みます。つまり、現在、すべての異なるファイル名をそれらのコンテンツ(およびそれらのアクセス許可)と結び付けており、Gitの「ディレクトリ」オブジェクトに相当するものを作成します:
$ git write-tree
これにより、結果のツリーの名前が出力されます。この場合(私が説明したとおり正確に実行した場合)、以下のようになっているべきです
8988da15d077d4829fc51d8544c097def6644dbb
これは、別のワケワカメなオブジェクト名です。繰り返しになりますが、あなたは必要に応じて、 git cat-file -t 8988d... を使用して、今回のオブジェクトが「ブロブ」オブジェクトではなく「ツリー」オブジェクトであることを確認できます( git cat-file を使用して、生のオブジェクトの内容を実際に出力することもできますが、主にごちゃごちゃしたバイナリが見れるだけであまり面白くありません)。
しかしながら、通常は git commit-tree コマンドを使用してツリーをコミットオブジェクトにコミットするため、通常は git write-tree を単独で使用することはありません。 実際のところ git write-tree を単独で使用するのではなく、その結果を git commit-tree へ引数として渡す方が簡単です。
git commit-tree は通常、いくつかの引数を取ります。 git commit-tree はコミットの「親」が何であるかを知りたいのですが、 これはこの新しいリポジトリーでの最初のコミットであり、親がないため、 ツリーのオブジェクト名を渡すだけで済みます。ただし、 git commit-tree は、標準入力からコミット・メッセージを取得することも必要です。 そしてコミットの結果のオブジェクト名を標準出力に書き出します。
ここで、 HEAD が指す .git/refs/heads/master ファイルを作成します。このファイルには、masterブランチのツリーのてっぺん(top-of-tree)への参照が含まれているはずです。これはまさに「git commit-tree」が吐き出すものなので、一連の単純なシェルコマンドでこれをすべて行うことができます:
$ tree=$(git write-tree)
$ commit=$(echo 'Initial commit' | git commit-tree $tree)
$ git update-ref HEAD $commit
この場合に限り、他の何にも関係のないまったく新しいコミットが作成されます。 通常、これはプロジェクトに対して「1回だけ」行います。このコミットはこの後のすべてのコミットの親になります。
繰り返しますが、通常、これを実際に手作業で行うことはありません。 これらすべてを実行する git commit という便利なスクリプトがあります。 したがって、あなたは代わりに git commit と記述すれば、上記の魔法のスクリプトが実行されます。
Making a change
以前、ファイル 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
(ここで、-p は git diff-files のと同じ意味です。) 同一の差異が表示されますが、理由はまったく異なります。これは、作業ツリーを、インデックスファイルではなく、作成したツリーと比較しています。たまたまこれら2つが明らかに同じであるため、私達は同じ結果を得たのです。
繰り返しになりますが、これは一般的な操作であるため、以下のように短縮することもできます
$ git diff HEAD
これで、結局は上記のようなことをやってくれます。
つまり、 git diff-index は通常、ツリーを作業ツリーと比較しますが、 --cached フラグを指定すると、 代わりにインデックス・キャッシュの内容と比較し、 現在の作業ツリーの状態を完全に無視するように指示します。 我々はインデックス・ファイルをHEADに書き込んだばかりなので、 git diff-index --cached -p HEAD を実行すると、 空の差分セットを返すはずで、 これは正に指示したとおりの結果です。
|
Note
|
これを理解するのは難しいことでは無く、すぐにあなたは、Gitが明示的に通知されていないファイルを知らない(または気にしない)ことに気付きます。Gitは比較するファイルを「探す」ことは決してありません。ファイルが何であるかを教えて貰えることを期待しており、それがインデックスの目的なのです。 |
ただし、私達の次のステップは、私達の行った変更をコミットすることです。繰り返しますが、何が起こっているのかを理解するために、「作業ツリーの内容」と「インデックスファイル」と「コミットされたツリー」の違いに注意してください。私達がコミットしたい作業ツリーに変更があり、私達は常にインデックスファイルを処理する必要があるため、したがって、私達が最初に行う必要があるのは、インデックスキャッシュを更新することです:
$ git update-index hello
(注意: Gitはこのファイルをすでに知っているので、 今回は --add フラグを必要としなかったことに注意してください)。
注意: ここでは、 さまざまな git diff-* バージョンの動作に注目してください。 インデックス内で hello を更新した後、 git diff-files -p は最早差分を表示しませんが、 git diff-index -p HEAD は依然として現在の状態がコミットした状態と異なることを示します。 実際、 今や --cached フラグを使用するかどうかに関係なく、 git diff-index は同一の差分を表示します。 これは、 今やインデックスが作業ツリーと一致しているからです。
これで、我々はインデックス内の hello を更新したので、我々は新しいバージョンをコミットできます。我々はもう一度手動でツリーを作成し、ツリーをコミットすることでそれを行うことができます(今回は、 -p HEAD フラグを使用して、HEADが新しいコミットの「親」であり、これが最初のコミットではなくなったことをコミットに通知する必要があります)。しかし、あなたはすでに一度この手作業を経験済みですので、今回は役立つスクリプトを使用してみましょう:
$ git commit
これにより、あなたがコミットメッセージを書き込むためのエディタが起動し、あなたがさっきやった事についてちょっぴり教えてくれます。
あなたが必要なメッセージを書き込むと、 # で始まるすべての行が削除され、残りはこの変更のコミットメッセージとして使用されます。あなたがこの時点で結局何もコミットしたくないと判断した場合(あなたは引き続き編集してインデックスを更新できます)、あなたは空のメッセージを残すことができます。それ以外の場合、 git commit は変更をコミットします。
あなたは今、 初めての本格的な Git コミットを行いました。 もし、git commit が実際に行っていることに興味があるなら、 お気軽に調べてみてください。 これは、 役立つ(かもしれない)コミット・メッセージ・ヘッダーを生成するための非常にシンプルなシェル・スクリプトと、 実際のコミットを行ういくつかのワンライナー(git commit)で構成されています。
Inspecting Changes
変更を作成することは便利ですが、後で何が変更されたかを知ることができればさらに便利です。このための最も便利なコマンドは、diffファミリーのもう1つ、つまり git diff-tree です。
git diff-tree には任意の2つのツリーを指定でき、それらの間の違いがわかります。けれども、おそらくもっと一般的には、あなたはコミットオブジェクトを1つだけ与えることができ、そうすると、そのコミット自体の親を把握し、違いを直接表示します。したがって、すでに数回見たのと同一のdiffを取得するために、以下のようにすることができます
$ git diff-tree -p HEAD
(繰り返しになりますが、 -p は人間が読めるパッチとして違いを表示することを意味します)、(HEAD が指す)最後のコミットが実際に何を変更したかを表示します。
|
Note
|
以下は、さまざまな
|
さらに興味深いことに、 git diff-tree に --pretty フラグを指定することもできます。これにより、コミットメッセージと作者とコミットの日付も表示され、一連のdiff全体を表示するように指示します。または、「黙って」(silent)と指示して、差分をまったく表示せずに実際のコミットメッセージを表示することもできます。
実際には、 (リビジョンのリストを生成する) git rev-list プログラムと一緒に使うことで、 git diff-tree は、 変更の豊かな源泉となります。 git rev-list の出力を git diff-tree --stdin にパイプする簡単なスクリプトを使用して、 git log や git log -p などを模擬できます。 これは正に初期バージョンの git log が実装されていた方法でした。
Tagging a version
Gitには、「軽い」(light)タグと「注釈付きタグ」(annotated tag)の2種類のタグがあります。
「軽い」タグは、 head と呼ぶ代わりに .git/refs/tags/ サブディレクトリに配置することを除いて、 技術的にはブランチ以上のものではありません。 したがって、最も単純な形式のタグは以下のようになります
$ git tag my-first-tag
これは、現在の HEAD を .git/refs/tags/my-first-tag ファイルに書き込むだけです。その後は、その特定の状態にこのシンボル名を使用できます。たとえば、以下のことができます
$ git diff my-first-tag
これは、 現在の状態をそのタグと比較します。 この時点では明らかに空の差分になりますが、 開発とコミットを続ければ、 タグを「アンカーポイント」として使用して、 タグを付けた時点から何が変更されたかを確認できます。
「注釈付きタグ」(annotated tag)は現実には実際のGitオブジェクトです。タグ付けする状態へのポインタだけでなく、小さなタグ名とメッセージ、およびオプションで「はい、あなたは実際にそのタグを作成した」というPGP署名もあります。これらの注釈付きタグは、 git tag に対して -a または -s フラグを使用して作成します:
$ git tag -s <tagname>
これは現在の HEAD に署名します(しかし、あなたはタグ付けするものを指定する別の引数を指定することもできます。 たとえば、 git tag <tagname> mybranch を使用して現在の mybranch ポイントにタグ付けすることができます)。
通常、 署名付きタグはメジャー・リリースやそのような重要な場面でのみ使用します。 一方、軽量タグ(light-weight tags)は、 任意のマーキングに便利です。 あなたが特定の時点を覚えておきたいと決めたときは、 いつでもプライベート・タグを作成して、 その時点の状態を象徴するわかりやすい名前を持つことができます。
Copying repositories
Gitリポジトリは通常、完全に自給自足で再配置可能です。たとえば、CVSとは異なり、「リポジトリ」と「作業ツリー」という別個の概念はありません。Gitリポジトリは通常、作業ツリーであり、ローカルのGit情報は .git サブディレクトリに隠されています。他には何もありません。あなたが見たそのままです。
|
Note
|
あなたは、追跡中のディレクトリからGitの内部情報を分割するようにGitに指示できますが、我々は今のところはそれを無視します。なぜならそれは、通常のプロジェクトの仕組みでなく、本当に特別な用途のためだけのものだからです。よって、「Git情報は、それが記述する作業ツリーに常に直接関連付けられている」という概念は技術的には100%正確ではない可能性がありますが、通常はそれで通ります。 |
これには2つの意味があります:
-
あなたが、このチュートリアル用リポジトリーに飽きてしまった場合(またはミスをして最初からやり直したい場合)、単純に以下のようにするだけです
$ rm -rf git-tutorialするとリポジトリーは無くなります。 外部リポジトリは存在せず、 作成したプロジェクト以外に履歴もありません。
-
あなたがGitリポジトリを移動または複製する場合は、あなたはそうすることができます。
gitcloneコマンドがありますが、リポジトリ(と、それに伴うすべての完全な履歴とともに)のコピーを作成するだけの場合は、通常のcp-agit-tutorialnew-git-tutorialを使用して作成できます。注意: Git リポジトリーを移動またはコピーした際、 Git のインデックス・ファイル(各種情報をキャッシュしており、 特にファイルの「stat」情報の一部を含む)は更新が必要になることがあります。 そのため、
cp-aで新しいコピーを作成した後、 以下を実行し$ git update-index --refreshインデックス・ファイルが最新の状態かどうか確認します。
注意: 2番目のポイントは、PC間でも当てはまることに注意してください。scpやrsyncやwget等々、通常のコピーメカニズムを使用してリモートGitリポジトリを複製できます。
リモートリポジトリをコピーするときは、あなたは、これを行うときに少なくともインデックスキャッシュを更新する必要があります。特に、他の人のリポジトリでは、インデックスキャッシュが既知の状態(あなたには、彼らが何をしたのか、まだチェックインしていないのかわかりません)にあることを確認する必要があるため、通常は 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 は、以前は上記2行を記述したものでした。 ただし、 git status や git commit などのいくつかは、 基本的な Git コマンドを中心とした少々複雑なスクリプトです。
多くの(ほとんどの?)公開リモート・リポジトリーには、 チェックアウトされたファイルやインデックス・ファイルすら含まれず、 実際のコア Git ファイルのみが含まれます。 このようなリポジトリには通常、 .git サブディレクトリすらなく、 すべての Git ファイルが直接リポジトリにあります。
このような「生の」Gitリポジトリのあなた独自のローカルな活きたコピー(local live copy)を作成するには、最初にプロジェクト用の独自のサブディレクトリを作成し、次に生のリポジトリの内容を .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 フラグは「すべてのファイルをチェックアウトする」ことを意味します(古いコピーまたはチェックアウトされたツリーの古いバージョンがある場合は、最初に -f フラグを追加して、 git checkout-index に古いファイルの上書きを「強制」するように指示する必要がある事があります)。
繰り返しますが、これはすべて以下のように簡略化できます
$ git clone git://git.kernel.org/pub/scm/git/git.git/ my-git
$ cd my-git
$ git checkout
これは正に上記のすべてを行います。
これで、 あなたは、 他の誰か誰か(またはあなた自身の)のリモート・リポジトリーをコピーし、 チェックアウトすることに成功しました。
Creating a new branch
Gitのブランチは、実際には .git/refs/ サブディレクトリ内からGitオブジェクトデータベースへのポインタにすぎません。すでに説明したように、HEAD ブランチはこれらのオブジェクトポインタの1つへのシンボリックリンクにすぎません。
あなたはプロジェクト履歴の任意のポイントを選択し、そのオブジェクトのSHA-1名を .git/refs/heads/ の下のファイルに書き込むだけで、いつでも新しいブランチを作成できます。任意のファイル名(実際にはサブディレクトリ名)を使用できますが、慣例では、「通常の」ブランチ(normal branch)は「master」と呼ばれます。 ただし、これは単なる慣例であり、強制されるものではありません。
これを例として示すために、以前使用したgit-tutorialリポジトリに戻り、その中にブランチを作成しましょう。これを行うには、あなたは新しいブランチをチェックアウトしたいと言うだけです:
$ git switch -c mybranch
現在の HEAD 位置に基づいて新しいブランチを作成し、それに切り替えます。
|
Note
|
あなたが、履歴の現在のHEAD以外の時点で新しいブランチを開始することを決定した場合は、
そうすると、これは以前のコミットにて新しいブランチ |
以下のように実行することで、あなたはいつでも元の master ブランチに戻ることができます
$ git switch master
(または、その他のブランチ名を指定します。) そして、あなたがたまたまどのブランチにいるのかを忘れた場合は、単純に以下のようにします。
$ cat .git/HEAD
とすると、それが指している場所を教えてくれます。あなたが持っているブランチのリストを取得するには、以下のようにします。
$ git branch
これは、以前は、 ls .git/refs/heads を囲む単純なスクリプトにすぎませんでした。それはそれとして、これは、現在使用しているブランチの前にアスタリスクが表示されます。
実際にチェックアウトして切り替えることなく、新しいブランチを作成したい場合があります。その場合は、以下のコマンドを使用してください
$ git branch <branchname> [startingpoint]
これは単にブランチを作成しますが、それ以上は何もしません。 その後 — そのブランチで実際に開発することを決定したら — 引数としてブランチ名を使用して、通常の `git switch `を使用してそのブランチに切り替えることができます。
Merging two branches
ブランチを持つことのアイデアの1つは、ブランチでいくつかの(おそらく実験的な)作業を行い、最終的にそれをメインブランチにマージすることです。したがって、元の master ブランチと同じである、上記の mybranch を作成したと仮定して、我々がそのブランチにいることを確認し、そこでいくつかの作業を行いましょう。
$ git switch mybranch
$ echo "Work, work, work" >>hello
$ git commit -m "Some work." -i hello
ここでは、 hello に別の行を追加し、 -i フラグ(コミット時にこれまでにインデックスファイルに対して行ったことに加えて、そのファイルを「含める」ようにGitに指示)を使用してファイル名を git commit に直接指定する、 git update-index hello と git commit の両方を実行するための省略形を使用しました。 -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
masterブランチもいい感じになってきました。
いまや、2つのブランチがあり、あなたは完了した作業をマージすることにしました。 その前に、何が起こっているのかを確認するのに役立つクールなグラフィカルツールを紹介しましょう:
$ gitk --all
あなたのブランチ(これが --all の意味です。通常は、現在の HEAD が表示されます)とその履歴の両方をグラフィカルに表示します。また、それらがどのようにして共通のソースから得られたのかを正確に確認することもできます。
さて、 gitk を終了します(^Q または File メニュー)。 そして、 あなたは mybranch ブランチで行った作業を、 現在 HEAD でもある master ブランチにマージしたいと決めます。 そのためには、 git merge という便利なスクリプトがあり、 どのブランチをマージするのか、 どのようなマージを行うのかを指定する必要があります:
$ git merge -m "Merge work in mybranch" mybranch
マージを自動的に解決できる場合は、 オプション -m で指定した引数がコミット・メッセージとして使用されます。
しかし、 今回は、 意図的にマージを手動で修正する必要がある状況を作りました。 そのため、 Git は可能な限り自動的にマージ(今回は、 mybranch ブランチで変更がなかった example ファイルのみが自動的にマージされる)を行った上で、 以下のようにいいます:
Auto-merging hello
CONFLICT (content): Merge conflict in hello
Automatic merge failed; fix conflicts and then commit the result.
これは、「自動マージ」(Automatic merge)を実行したら hello の競合(CONFLICT)が原因で失敗(Automatice maerge failed)したと表示しています。
でも心配無用。これにより、あなたがCVSを使ったことがあるなら既に慣れている形式で hello に競合を残したので、エディタで hello を開いて、なんとかして修正しましょう。私は `hello`に4行すべてが含まれるようにすることを提案することにします:
Hello World
It's a new day for git
Play, play, play
Work, work, work
手動マージに満足したら、以下の手順を実行します。
$ git commit -i hello
非常に大きな警告で、 マージをコミットしていることを知らせます(これは正しいので、気にしないでください)(訳注: 手元の 2.41 ではエディター内のコメントとして表示される)。そして、 エディター上で git merge の世界での冒険について、 短いマージ・メッセージを書くことができます。
作業が終わったら、 gitk --all を起動して、 履歴がグラフィカルにどう見えるかを確認してください。 mybranch がまだ存在していることに注意してください。 必要であれば、 mybranch に切り替えて作業を続けることができます。 mybranch ブランチにはマージが含まれていませんが、 次に master ブランチからマージする際、 Gitはそれがどのようにマージされたかを知っているため、 そのマージを再度行う必要はありません。
X-Window環境で常に作業するわけではない場合、もう1つの便利ツールは、 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 ブランチに含まれるコミットと、 mybranch ブランチの行の2桁目(+)を表示するために使用されます。3つのコミットがタイトルとともに表示されます。それらはすべて1桁目に空白以外の文字があり( * は現在のブランチでの通常のコミットを示し、- はマージコミットです)、これはそれらが現在 master ブランチの一部であることを意味します。 mybranch はmasterブランチからのこれらのコミットを組み込むためにマージされていないため、「Some work」コミットのみが2桁目にプラスの + 文字を持っています。コミットログメッセージの前の括弧内の文字列は、コミットに名前を付けるために使用できる短い名前です。上記の例では、 "master" と "mybranch" がブランチヘッドです。 "master^" は "master" ブランチヘッドの最初の親です。より複雑なケースを確認したい場合は、 gitrevisions(7) を参照してください。
|
Note
|
--more=1 オプションがないと、 [mybranch] コミット は master と mybranch の両方の先端の共通の祖先であるため、 git show-branch は [master^] コミットを出力しません。 詳細については、 git-show-branch(1) を参照してください。 |
|
Note
|
マージ後に master ブランチにさらにコミットがあった場合、git show-branch はデフォルトではマージコミット自体を表示しません。この場合、マージコミットを表示するには、 --sparse オプションを指定する必要があります。 |
さて、 今やあなたが mybranch で全ての作業を行い終えて、 その努力の成果がようやく master ブランチにマージされたと仮定しましょう。 あなたは mybranch に戻り、 git merge を実行して「上流の変更」(upstream changes)をあなたのブランチに取り込みます。
$ 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
Merging external work
通常、自分のブランチとマージするよりも、他の誰かのとマージする方がはるかに一般的です。そして、Gitを使用すると、これも非常に簡単になります。事実、 git merge を実行するのとそれほど違いはありません。 実際、リモートマージは、「リモートリポジトリから一時的タグ(temporary tag)に作業をフェッチする」だけで、その後に git merge が続きます。
リモート・リポジトリーからの取得(fetching)は、 ご想像の通り、 git fetch によって行われます:
$ git fetch <remote-repository>
以下の転送方法(transport)のいずれかを使用して、ダウンロード元のリポジトリに名前を付けることができます:
- SSH
-
remote.machine:/path/to/repo.git/またはssh://remote.machine/path/to/repo.git/この転送方法(transport)はアップロードとダウンロードの両方に使用でき、リモートマシンへの
sshを介したログイン権限が必要です。転送元と転送先が持つヘッドコミットを交換し、オブジェクトの最小組を(限定的に)転送することにより、反対側に欠けているオブジェクトの組を見つけます。これは、リポジトリ間でGitオブジェクトを交換するための最も効率的な方法です。 - ローカルディレクトリ
-
/path/to/repo.git/この転送方法はSSH転送と同じですが、
sshを介してリモートマシンでもう一方の端を実行する代わりに、shを使用してローカルマシンで転送元と転送先で実行します。 - Gitネイティブ
-
git://remote.machine/path/to/repo.git/この転送方法は、匿名(anonymous)でダウンロードするために設計されました。SSH転送と同様に、ダウンストリーム側にないオブジェクトの組を検出し、最小のオブジェクトの組を(限定的)転送します。
- HTTP(S)
-
http://remote.machine/path/to/repo.git/http URL や https URL からのダウンローダーは、最初に、
repo.git/refs/ディレクトリの下にある指定のref名を調べて、リモートサイトから最上位のコミットオブジェクト名を獲得し、それから、そのコミットオブジェクトのオブジェクト名を使用してrepo.git/objects/xx/xxx... からダウンロードすることにより、コミットオブジェクトの取得を試みます。次に、そのコミットオブジェクトを読み取り、その親コミットと関連するツリーオブジェクトを見つけます。必要なすべてのオブジェクトを取得するまで、このプロセスを繰り返します。この動作のため、これらは「コミットウォーカー」(commit walkers)と呼ばれることもあります。「コミットウォーカー」(commit walkers)は、Gitネイティブ転送のようにGit対応のスマートサーバーを必要としないため、「バカ転送」(dumb transports)と呼ばれることもあります。ディレクトリインデックスさえサポートしていないストックHTTPサーバーで十分です。 ただし、バカ転送ダウンローダー(dumb transport downloaders)を支援するには、
gitupdate-server-infoを使用してリポジトリを準備する必要があります。
リモートリポジトリからフェッチしたら、あなたはそれを現在のブランチに「マージ」します。
ただし、「フェッチ」してすぐに「マージ」するのは非常に一般的なことなので、 git pull を使えば簡単に実行できます:
$ git pull <remote-repository>
そして、オプションで、リモート側のブランチ名を2番目の引数として指定します。
|
Note
|
あなたは、 ブランチを全く使わず、 必要なブランチの数だけローカル・リポジトリーを保持し、 git pull を使ってそれらの間でマージすることで作業することもできます。 これはブランチ間でマージするのと同じです。 このアプローチの利点は、 各「ブランチ」ごとにチェックアウトされたファイルのセットを保持できるため、 複数の開発ラインを同時に扱う場合に切り替えが簡単になる可能性があることです。 もちろん、 複数の作業ツリーを保持するためにディスク使用量が増えるという代償を払いますが、 最近ではディスク容量は安価です。 |
あなたは同じリモートリポジトリからしばしばプルする可能性があります。なので略記法として、リモートリポジトリのURLをローカルリポジトリの設定ファイルに以下のように保存できます:
$ git config remote.linus.url https://git.kernel.org/pub/scm/git/git.git/
そして、完全なURLの代わりに git pull で "linus" キーワードを使用します。
例:
-
gitpulllinus -
gitpulllinustagv0.99.1
上記は以下と同じです:
-
gitpullhttp://www.kernel.org/pub/scm/git/git.git/HEAD -
gitpullhttp://www.kernel.org/pub/scm/git/git.git/tagv0.99.1
How does the merge work?
このチュートリアルでは、動作しない磁器コマンドに対処するために配管コマンドがどのように機能するかを示していますが、これまでのところ、マージが実際にどのように機能するかについては説明していません。あなたがこのチュートリアルを初めて読む時は、「Publishing your work」セクションにスキップして、後でここに戻ってくることをお勧めします。
上記を了承したとして、続きをやります。例を示すために、 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^] second commit.
これで、手動でマージを試す準備ができました。
git merge コマンドは、2つのブランチをマージするときに、3方向マージアルゴリズムを使用します。 まず、それらの間の共通の祖先を見つけます。使用するコマンドは git merge-base です:
$ mb=$(git merge-base HEAD mybranch)
このコマンドは、共通の祖先のコミットオブジェクト名を標準出力に書き込むため、我々は次のステップで使用するため、その出力を変数にキャプチャしました。ちなみに、今回は、共通の祖先コミットは「second commit.」コミットです。あなたはそれを以下のように言うことができます:
$ git name-rev --name-only --tags $mb
my-first-tag
共通の祖先コミットを見つけた後の、2番目のステップは以下のとおりです:
$ git read-tree -m -u $mb HEAD mybranch
これは、すでに見たのと同じ git read-tree コマンドですが、前の例とは異なり、3つのツリーが必要です。 これにより、各ツリーの内容がインデックスファイルの異なる「ステージ」に読み込まれます(最初のツリーはステージ1に、2番目はステージ2に、等)。3つのツリーを3つのステージに読み取った後、3つのステージで同じパスがある場合はステージ0に「折りたたまれます」(collapsed)。また、3つのステージのうちの2つで同じパスは、ステージ0に折りたたまれ、ステージ1と異なるステージ2またはステージ3のいずれかからSHA-1を取得します(つまり、共通の祖先から片側だけが変更されます)。
「折りたたみ」(collapsing)操作の後、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方向マージを使用して、これら3つのバージョンのファイルをマージすることです。これは、 git merge-index コマンドの引数の1つとして 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方向マージを実行します。今回は、 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(つまり、あなたのバージョン)との違いであることに注意してください。
Publishing your work
さて、 あなたは、 誰かのリモート・リポジトリーの作業を利用することができるようにはなりました。 しかし、 「あなた」が他の人にプルしてもらえるようにリポジトリーを準備するにはどうすればよいでしょうか?
実際の作業は、 プライマリ・リポジトリーが .git サブディレクトリとしてぶら下がっている作業ツリーで行います。 そのリポジトリーをリモートでアクセス可能にして、 他の人にそこからプルするよう依頼することもできますが、 実際には通常はそのようには行われません。 推奨される方法は、 公開リポジトリーを作成し、 他の人がアクセスできるようにして、 プライマリ作業ツリーで加えた変更が良好な状態になったら、 そこから公開リポジトリーを更新することです。 これはよく『プッシュ』(push)と呼ばれます。
|
Note
|
この公開リポジトリーはさらにミラーリングされる可能性があり、それが kernel.org のGitリポジトリの管理方法です。 |
ローカル(プライベート)リポジトリーからリモート(公開)リポジトリーに変更を公開するには、リモート・マシンでの書き込み権限が必要です。 単一のコマンド git-receive-pack を実行するには、そこにSSHアカウントが必要です。
まず、 リモート・マシン上に空の公開リポジトリーを作成する必要があります。 この空のリポジトリーは、 後でプッシュすることで内容が追加され、 最新の状態に保たれます。 明らかに、 このリポジトリーの作成は一度だけ行う必要があります。
|
Note
|
git push は、ローカルマシンでは git send-pack 、リモートマシンでは git-receive-pack というコマンドのペアを使用します。ネットワークを介した2つのコマンドの間の通信は、内部でSSH接続を使用します。 |
あなたのプライベート・リポジトリーのGitディレクトリは通常 .git ですが、公開リポジトリーはプロジェクト名にちなんで名付けられていることがよくあります。つまり <project>.git です。 プロジェクト my-git のこのような公開リポジトリーを作成しましょう。リモートマシンにログインした後、以下のように空のディレクトリを作成します:
$ mkdir my-git.git
次に、 git init を実行してそのディレクトリを Git リポジトリーにしますが、 今回はその名前が通常の .git ではないため、 我々は少し異なる方法で処理します:
$ GIT_DIR=my-git.git git init
あなたが選択した転送方法(transport)を介して変更をプルする他のユーザーがこのディレクトリを使用できることを確認してください。 また、 あなたは $PATH 上に git-receive-pack プログラムがあることを確認する必要があります。
|
Note
|
sshdの多くのインストールでは、あなたがプログラムを直接実行するときに、ログインシェルとしてあなたのシェルが呼び出されません。これが意味するのは、ログインシェルが bash の場合、 .bashrc のみが読み取られ、 .bash_profile は読み取られないということです。 回避策として、あなたが git-receive-pack プログラムを実行できるように、 .bashrc で $PATH を設定していることを確認してください。 |
|
Note
|
あなたがこのリポジトリを公開して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/
Packing your repository
以前、私達は作成したGitオブジェクトごとに .git/objects/??/ ディレクトリの下に1つのファイルが保存されていることを確認しました。この表現は、アトミックかつ安全に作成するには効率的ですが、ネットワークを介した転送にはそれほど便利ではありません。Gitオブジェクトは、作成されると不変であるため、「一緒にパックする」ことでストレージを最適化する方法があります。以下のコマンド
$ git repack
は、あなたのためにそれをします。チュートリアルの例に従うと、これまでに約17個のオブジェクトが .git/objects/??/ ディレクトリに蓄積されているはずです。 git repack は、パックしたオブジェクトの数を示し、パックされたファイルを .git/objects/pack ディレクトリに保存します。
|
Note
|
.git/objects/pack ディレクトリに pack-*.pack と pack-*.idx の2つのファイルがあります。これらは互いに密接に関連しているため、何らかの理由で手動で別のリポジトリにコピーする場合は、必ず一緒にコピーする必要があります。前者はパック内のオブジェクトからのすべてのデータを保持し、後者はランダムアクセスのためのインデックスを保持します。 |
あなたが病的なほど疑り深い場合は、 git verify-pack コマンドを実行すると、破損したパックがあるかどうかが検出されますが、あまり心配する必要はありません。私たちのプログラムは常に完璧です ;-)
オブジェクトをパックしたら、既にパックファイルに取り込まれている、パックされていないオブジェクトを残す必要はありません。
$ git prune-packed
これは、あなたのためにそれらを削除します。
あなたがもの好きならば、git prune-packed を実行する前後に find .git/objects -type f を実行してみてください。 また、 git count-objects は、リポジトリ内でパックされていないオブジェクト(unpacked objects)の数と、それらが消費しているスペースの量を示します。
|
Note
|
git pull は HTTP 転送ではやや面倒です。 パックされたリポジトリーには、 比較的大きなパック内に比較的少ないオブジェクトしか含まれていない場合があります。 公開リポジトリから多くの HTTP プルが予想される場合は、 頻繁に再パックと刈り込み(prune)を行うか、 まったく行わないかを検討する事をお勧めします。 |
この時点で再度 git repack を実行すると、「Nothing new to pack.」(新しくパックするものはありません)と表示されます。あなたが開発を続行して変更を蓄積してから、 git repack を再度実行すると、前回リポジトリをパックして以降に作成されたオブジェクトを含む新しいパックが作成されます。最初のインポートの直後にプロジェクトをパックし(プロジェクトを最初から開始する場合を除く)、プロジェクトの活発度度に応じて、時々 git repack を実行することをお勧めします。
リポジトリが git push と git pull を介して同期される場合、転送元リポジトリにてパックされてたオブジェクトは通常、転送先ではアンパックされて保存されます。これにより、転送元と転送先で異なるパッキング戦略を使用できますが、両方のリポジトリを時々再パックする必要がある場合もあります。
Working with Others
Git は本当の意味で分散されたシステムですが、 プロジェクトを開発者の非公式な階層で整理すると便利な場合があります。 Linuxカーネルの開発はそのように行われています。 Randy Dunlap’s presentation の (17ページ "Merges to Mainline") のにイラストが良い例です。
この階層は純粋に「非公式」であることを強調しておく必要があります。この階層が意味する「パッチフローのチェーン」を強制するようなGitでの必須のものは何もありません。あなたはただ1つのリモートリポジトリからプルする必要はありません。
「プロジェクト指揮」(project lead)の推奨作業フローは以下のようになります:
-
あなたのローカルマシンで、あなたのプライマリリポジトリを準備します。あなたの作業はそこで行います。
-
他の人がアクセスできる公開リポジトリを準備します。
他の人がバカ転送プロトコル(dumb transport protocols)(HTTP)を介してリポジトリからプルしている場合、このリポジトリを「バカ転送フレンドリー」(dumb transport friendly)に保つ必要があります。
gitinit後、標準テンプレートからコピーした$GIT_DIR/hooks/post-update.sampleには、gitupdate-server-infoの呼び出しが含まれますが、あなたはmvpost-update.samplepost-updateを使用してフックを手動で有効にする必要があります。これにより、gitupdate-server-infoが必要なファイルを最新の状態に保つことができます。 -
あなたのプライマリリポジトリから公開リポジトリにプッシュします。
-
公開リポジトリを
gitrepackします。これにより、オブジェクトの初期セットをベースラインとして含む大きなパックが確立されます。リポジトリからのプルに使用される転送方法(transport)がパックされたリポジトリ(packed repositories)をサポートしている場合は、gitpruneが使える可能性があります。 -
あなたはプライマリリポジトリで作業を続けます。あなたの変更は、あなた独自の変更や、電子メールで受信するパッチや、「サブシステム保守者」の「公開」リポジトリをプルした結果のマージを含んでいます。
あなたはこのプライベートリポジトリはいつでも再パックできます。
-
あなたの変更を公開リポジトリにプッシュし、公開します。
-
時々、公開リポジトリを
gitrepackします。 手順5に戻り、作業を続行します。
そのプロジェクトに取り組んでいて、独自の「公開リポジトリ」を持つ「サブシステム保守者」に推奨される作業サイクルは以下のようになります:
-
「プロジェクト指揮」の公開リポジトリ上で
gitcloneを実行して、あなたの作業リポジトリを準備します。初期クローン作成に使用されるURLは、 remote.origin.url 構成変数に格納されます。 -
「プロジェクト指揮」の人と同じように、他の人がアクセスできる公開リポジトリを準備します。
-
「プロジェクト指揮」リポジトリが同じマシン上にある場合を除き、パックされたファイルを「プロジェク指揮」の公開リポジトリからあなたの公開リポジトリにコピーします。後者の場合、あなたは
objects/info/alternatesファイルを使用して、借用しているリポジトリを指すことができます。 -
あなたのプライマリリポジトリから公開リポジトリにプッシュします。
gitrepackを実行し、そして、リポジトリからのプルに使用される転送方法(transport)がパックされたリポジトリ(packed repositories)をサポートしている場合はgitpruneを実行します。 -
あなたのプライマリリポジトリで作業を続けます。あなたの変更には、あなた独自の変更や、電子メールで受信するパッチや、「プロジェクト指揮」と場合によっては「サブサブシステム保守者」の「公開」リポジトリをプルした結果のマージが含まれます。
あなたはこのプライベートリポジトリはいつでも再パックできます。
-
あなたの変更をあなたの公開リポジトリにプッシュし、「プロジェクト指揮」と、場合によっては「サブサブシステム保守者」にプルするように依頼します。
-
時々、公開リポジトリを
gitrepackします。 手順5に戻り、作業を続行します。
「公開」リポジトリを持たない「個人開発者」に推奨される作業サイクルは多少異なります。以下のようになります:
-
「プロジェクト指揮」(またはサブシステムで作業している場合は「サブシステム保守者」)の公開リポジトリを
gitcloneして、あなたの作業リポジトリを準備します。初期クローン作成に使用されるURLは、 remote.origin.url 構成変数に格納されます。 -
あなたは、 あなたのリポジトリーの
masterブランチで作業を行います。 -
時々、あなたの上流の公開リポジトリーに対して
gitfetchoriginを実行します。 これはgitpullの前半のみを実行しますが、 マージはしません。 公開リポジトリーのヘッドは.git/refs/remotes/origin/masterに保存されています。 -
gitcherryoriginを使用して、どのパッチが受け入れられたかを確認したり、gitrebaseoriginを使用して、あなたのマージされていない変更を更新された上流に転送します。 -
gitformat-patchoriginを使用して、上流への電子メール送信用のパッチを準備し、送信します。 手順2に戻り、作業を続行します。
Working with Others, Shared Repository Style
あなたがCVS界隈から来ていたら、前のセクションで提案された協力のスタイルはあなたにとって新しいものかもしれません。でも心配する必要はありません。 Gitは、おそらくあなたもよく知っている「共有公開リポジトリ」(shared public repository)スタイルの協同作業をサポートしています。
詳細については gitcvs-migration(7) を参照してください。
Bundling your work together
あなたは一度に複数のことに取り組む可能性があります。Gitでブランチを使用すると、これらの多かれ少なかれ独立したタスクを簡単に管理できます。
私達は2つのブランチを使用した「fun and work」の例で、ブランチがどのように機能するかはすでに見てきました。ブランチが3つ以上ある場合も、考え方は同じです。 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つのブランチを現在のブランチに一度にマージすることができます。まずは、今行ったことを元に戻して、最初からやり直しましょう。 master~2 にリセットすることにより、これら2つのマージの前のmasterブランチを取得する必要があります:
$ 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)をやるべきではない、ということに注意してください。タコ足(octopus)は有効な方法であり、2つ以上の独立した変更を同時にマージする場合、コミット履歴を簡単に表示できることがよくあります。しかし、マージしているブランチのいずれかとマージの競合があり、手動で解決する必要がある場合は、これは、これらのブランチで発生した開発が結局独立していないことを示しています。そうすると、あなたは、どうして一度に2つをマージして、競合をどのように解決したか、および一方の側で行われた変更をもう一方の側よりも優先した理由を文書化しなければならなくなります。そうしないと、プロジェクトの履歴を追跡するのが難しくなり、簡単ではなくなります。
SEE ALSO
GIT
Part of the git(1) suite