SYNOPSIS
git *
DESCRIPTION
このチュートリアルでは、「コア」(core)Gitコマンドを使用してGitリポジトリを設定および操作する方法について説明します。
あなたがGitをリビジョン管理システムとして使用する必要がある場合は、「A Tutorial Introduction to Git」( linkgit: gittutorial[7] )または the Git User Manual から始めることをお勧めします。
しかしながら、Gitの内部を理解したい場合は、これら低レベルのツールを理解しておくと役に立ちます。
コアGit(core Git)は「配管」(plumbing)と呼ばれることが多く、その上に「磁器」(porcelain)と呼ばれるより美しいユーザーインターフェイスがあります。配管コマンドを直接使用することはあまりありませんが、磁器コマンドが使えない時に配管コマンドでどうするかを知っておくのは良い事です。
このドキュメントが最初に作成されたとき、多くの磁器コマンドはシェルスクリプトでした。説明を簡単にするために、配管がどのように組み合わされて磁器コマンドを形成するかを示す例としていまだそれらを使用しています。ソースツリーには、参照用に contrib/examples/ にこれらのスクリプトの一部が含まれています。これらは最早シェルスクリプトとして実装されなくなりましたが、それでも、配管レイヤーコマンドの機能の説明は引き続き有効です。
Note
|
より深い技術的な詳細は、多くの場合 Note 欄になっています。最初の読書ではスキップしてかまいません。 |
Creating a Git repository
新しい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つのエントリが表示されます:
-
ref: refs/heads/master
という内容のHEAD
というファイル。これはシンボリックリンクに似ており、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バイトのファイルが多数表示されるはずです。 |
Note
|
上級ユーザーは、このチュートリアルを終了した後、 gitrepository-layout(5) を確認することをお勧めします。 |
これで、あなたの最初のGitリポジトリが作成されました。もちろん、空なのであまり役に立ちません。なので、データの入力を始めましょう。
Populating a Git repository
我々はシンプルかつ愚直に行きたいと思います、まずは簡単なファイルをいくつか入力して、その感触をつかむことから始めます。
あなたのGitリポジトリに保持したいランダムファイルを作成することから始めます。これがどのように機能するかを理解するために、いくつかの悪い例から始めます:
$ echo "Hello World" >hello
$ echo "Silly example" >example
これで、あなたの作業ツリー(working tree)(別名「作業ディレクトリ」(working directory))に2つのファイルが作成されましたが、実際にあなたの作業をチェックインするには、以下の2つの手順を実行する必要があります:
-
index ファイル(別名 cache )に作業ツリーの状態に関する情報を入力します。
-
そのインデックスファイルをオブジェクトとしてコミットします。
最初のステップは至極簡単です。作業ツリーへの変更について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
とは異なり、 gitdiff-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-{asterisk}
バージョンには何が起こるでしょうか。我々がインデックス内の hello
を更新した後、 git diff-files -p
は、違いを示さなくなりましたが、 git diff-index -p HEAD
は、現在の状態がコミットした状態とは異なることを示しています。 実際、 git diff-index
は、 --cached
フラグを使用するかどうかに関係なく同一の違いを示します。これは、インデックスは作業ツリーと一貫性があるためです。
これで、我々はインデックス内の 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種類のタグがあります。
「軽い」タグは、 ヘッドと呼ぶ代わりに .git/refs/tags/
サブディレクトリに配置することを除いて、技術的にはブランチ以上のものではありません。したがって、最も単純な形式のタグは以下のようになります
$ git tag my-first-tag
これは、現在の HEAD を .git/refs/tags/my-first-tag
ファイルに書き込むだけです。その後は、その特定の状態にこのシンボル名を使用できます。たとえば、以下のことができます
$ git diff my-first-tag
あなたの現在の状態をそのタグとdiffします。この時点では明らかに空のdiffになりますが、あなたが開発とコミットを続ければ、タグを「アンカーポイント」として使用して、あなたがタグを付けてから何が変更されたかを確認できます。
「注釈付きタグ」(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リポジトリを移動または複製する場合は、あなたはそうすることができます。
git clone
コマンドがありますが、リポジトリ(と、それに伴うすべての完全な履歴とともに)のコピーを作成するだけの場合は、通常のcp -a git-tutorial new-git-tutorial
を使用して作成できます。注意: Gitリポジトリを移動またはコピーした場合、Gitインデックスファイル(さまざまな情報、特に関連するファイルの「統計」情報の一部をキャッシュする)を更新する必要がある可能性があることに注意してください。したがって、
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
ブランチで行った作業を 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.
これは、「自動マージ」(Automatic merge)を実行したら hello
の競合が原因で失敗したと表示しています。
でも心配無用。これにより、あなたがCVSを使ったことがあるなら既に慣れている形式で hello
に競合を残したので、エディタで 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環境で常に作業するわけではない場合、もう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 を実行して、「アップストリームの変更」をあなたのブランチに戻しましょう。
$ 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
が続きます。
リモートリポジトリからのフェッチは、ご想像の通り、 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)を支援するには、
git update-server-info
を使用してリポジトリを準備する必要があります。
リモートリポジトリからフェッチしたら、あなたはそれを現在のブランチに「マージ」します。
ただし、「フェッチ」してすぐに「マージ」するのは非常に一般的なことなので、 git pull
を使えば簡単に実行できます:
$ git pull <remote-repository>
そして、オプションで、リモート側のブランチ名を2番目の引数として指定します。
Note
|
あなたが持ちたいブランチの数のローカルリポジトリを保持し、ブランチ間のマージと同様に git pull でそれらの間のマージを行うことで、ブランチを全く使用しないことも可能です。このアプローチの利点は、チェックアウトされた各々「ブランチ」のファイルの組を保持できることです。複数の開発ラインを同時に調整すると、切り替えが簡単になる場合があります。もちろん、複数の作業ツリーを保持するためにディスク使用量を増やすという代償を払うことにはなりますが、最近のディスク容量は安価です。 |
あなたは同じリモートリポジトリからしばしばプルする可能性があります。なので略記法として、リモートリポジトリのURLをローカルリポジトリの設定ファイルに以下のように保存できます:
$ git config remote.linus.url http://www.kernel.org/pub/scm/git/git.git/
そして、完全なURLの代わりに git pull
で "linus" キーワードを使用します。
例:
-
git pull linus
-
git pull linus tag v0.99.1
上記は以下と同じです:
-
git pull http://www.kernel.org/pub/scm/git/git.git/ HEAD
-
git pull http://www.kernel.org/pub/scm/git/git.git/ tag v0.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
サブディレクトリとしてその下にぶら下がっているあなたの作業ツリーで実際の作業を行っています。あなたはそのリポジトリにリモートでアクセスできるようにして、そこからプルするように人々に依頼することも可能ですが、実際には、それは通常の方法ではありません。推奨される方法は、パブリックリポジトリを用意し、他の人がアクセスできるようにすることです。あなたのプライマリ作業ツリーに加えた変更がよい状態になったら、そこからパブリックリポジトリを更新します。これはしばしば「プッシュ」(pushing)と呼ばれます。
Note
|
このパブリックリポジトリはさらにミラーリングされる可能性があり、それが kernel.org のGitリポジトリの管理方法です。 |
ローカル(プライベート)リポジトリからリモート(パブリック)リポジトリに変更を公開するには、リモートマシンでの書き込み権限が必要です。単一のコマンド git-receive-pack
を実行するには、そこにSSHアカウントが必要です。
まず、パブリックリポジトリを格納する空のリポジトリをリモートマシンに作成する必要があります。この空のリポジトリは、後でプッシュすることで、データが設定され、最新の状態に保たれる。明らかに、このリポジトリの作成は1度だけ実行する必要があります。
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
|
パックされたリポジトリでは比較的大きなパックに含まれるオブジェクトが比較的少ない可能性があるため、HTTP転送(transport)での git pull は少々面倒です。パブリックリポジトリから多くのHTTPプルが予想される場合は、頻繁に再パックして整理するか、まったく行わないことをお勧めします。 |
この時点で再度 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)に保つ必要があります。
git init
後、標準テンプレートからコピーした$GIT_DIR/hooks/post-update.sample
には、git update-server-info
の呼び出しが含まれますが、あなたはmv post-update.sample post-update
を使用してフックを手動で有効にする必要があります。これにより、git update-server-info
が必要なファイルを最新の状態に保つことができます。 -
あなたのプライマリリポジトリから公開リポジトリにプッシュします。
-
公開リポジトリを
git repack
します。これにより、オブジェクトの初期セットをベースラインとして含む大きなパックが確立されます。リポジトリからのプルに使用される転送方法(transport)がパックされたリポジトリ(packed repositories)をサポートしている場合は、git prune
が使える可能性があります。 -
あなたはプライマリリポジトリで作業を続けます。あなたの変更は、あなた独自の変更や、電子メールで受信するパッチや、「サブシステム保守者」の「公開」リポジトリをプルした結果のマージを含んでいます。
あなたはこのプライベートリポジトリはいつでも再パックできます。
-
あなたの変更を公開リポジトリにプッシュし、公開します。
-
時々、公開リポジトリを
git repack
します。 手順5に戻り、作業を続行します。
そのプロジェクトに取り組んでいて、独自の「公開リポジトリ」を持つ「サブシステム保守者」に推奨される作業サイクルは以下のようになります:
-
「プロジェクト指揮」の公開リポジトリ上で
git clone
を実行して、あなたの作業リポジトリを準備します。初期クローン作成に使用されるURLは、 remote.origin.url 構成変数に格納されます。 -
「プロジェクト指揮」の人と同じように、他の人がアクセスできる公開リポジトリを準備します。
-
「プロジェクト指揮」リポジトリが同じマシン上にある場合を除き、パックされたファイルを「プロジェク指揮」の公開リポジトリからあなたの公開リポジトリにコピーします。後者の場合、あなたは
objects/info/alternates
ファイルを使用して、借用しているリポジトリを指すことができます。 -
あなたのプライマリリポジトリから公開リポジトリにプッシュします。
git repack
を実行し、そして、リポジトリからのプルに使用される転送方法(transport)がパックされたリポジトリ(packed repositories)をサポートしている場合はgit prune
を実行します。 -
あなたのプライマリリポジトリで作業を続けます。あなたの変更には、あなた独自の変更や、電子メールで受信するパッチや、「プロジェクト指揮」と場合によっては「サブサブシステム保守者」の「公開」リポジトリをプルした結果のマージが含まれます。
あなたはこのプライベートリポジトリはいつでも再パックできます。
-
あなたの変更をあなたの公開リポジトリにプッシュし、「プロジェクト指揮」と、場合によっては「サブサブシステム保守者」にプルするように依頼します。
-
時々、公開リポジトリを
git repack
します。 手順5に戻り、作業を続行します。
「公開」リポジトリを持たない「個人開発者」に推奨される作業サイクルは多少異なります。以下のようになります:
-
「プロジェクト指揮」(またはサブシステムで作業している場合は「サブシステム保守者」)の公開リポジトリを
git clone
して、あなたの作業リポジトリを準備します。初期クローン作成に使用されるURLは、 remote.origin.url 構成変数に格納されます。 -
あなたは、あなたのリポジトリの master ブランチで作業を行います。
-
時々、あなたのアップストリームの公開リポジトリから
git fetcho rigin
を実行します。これはgit pull
の前半のみを実行しますが、マージはしません。公開リポジトリのヘッドは.git/refs/remotes/origin/master
に保存されています。 -
git cherry origin
を使用して、どのパッチが受け入れられたかを確認したり、git rebase origin
を使用して、あなたのマージされていない変更を更新されたアップストリームに転送します。 -
git format-patch origin
を使用して、アップストリームへの電子メール送信用のパッチを準備し、送信します。 手順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