Git
英語 ▾ トピック ▾ 最新版 ▾ gitcore-tutorial 最終更新日: 2.43.1

NAME

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

SYNOPSIS

git *

DESCRIPTION

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

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

ただし、これらの低レベルツールを理解することで、Gitの内部動作を理解するのに役立ちます。

Gitコアはしばしば「プルーミング(内部コマンド)」と呼ばれ、その上に構築されたより使いやすいユーザーインターフェースは「ポーセリン(外部コマンド)」と呼ばれます。 プルーミングを直接使用する必要はあまりないかもしれませんが、ポーセリンがうまく機能しない場合に、プルーミングが何をしているのかを知っておくと役立つ場合があります。

このドキュメントが最初に作成された当時、多くのポーセリンコマンドはシェルスクリプトでした。簡潔にするため、プルーミングがどのように組み合わされてポーセリンコマンドを形成するかを示す例として、引き続きシェルスクリプトを使用しています。 ソースツリーには、参照用のこれらのスクリプトの一部が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つのエントリが表示されます。

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

    HEADリンクが指すファイルがまだ存在しないことを心配する必要はありません。HEAD開発ブランチを開始するコミットはまだ作成していません。

  • プロジェクトのすべてのオブジェクトを含むobjectsというサブディレクトリ。オブジェクトを直接見る必要がある本当の理由はほとんどありませんが、これらのオブジェクトがリポジトリのすべての実際のデータを含むものであることを知っておくと役立つ場合があります。

  • 参照を含むrefsというサブディレクトリ。

特に、refsサブディレクトリには、それぞれheadstagsという名前の2つのサブディレクトリが含まれています。それらは名前が示す通りに機能します。それらは、任意の数の異なる開発のヘッド(別名ブランチ)、およびリポジトリ内の特定のバージョンを命名するために作成した任意のタグへの参照を含んでいます。

1つの注意点:特別な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に別の行を追加することで発生した変更の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

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ファイルを作成します。このファイルには、マスターブランチのツリーの先端への参照が含まれている必要があり、それがgit commit-treeが出力するものであるため、一連の簡単なシェルコマンドですべて実行できます。

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

この場合、他のものとは無関係に完全に新しいコミットが作成されます。通常、これはプロジェクトで一度だけ実行し、それ以降のすべてのコミットは以前のコミットの上に配置されます。

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

変更を行う

ファイルhellogit 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 update-index hello

(Gitが既にファイルを知っていたため、今回は--addフラグが不要なことに注意してください)。

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

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

$ git commit

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

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

これで、最初の実際のGitコミットを行いました。git commitが実際に行うことを知りたい場合は、自由に調査してください。これは、便利な(?)コミットメッセージヘッダーを生成するための非常に単純なシェルスクリプトと、コミット自体を実行するいくつかの1行のコード(git commit)です。

変更の検査

変更を作成することは便利ですが、後で何が変更されたかを判断できる方がさらに便利です。これには、diffファミリーの別のもの、つまりgit diff-treeが最も役立ちます。

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

$ git diff-tree -p HEAD

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

注記

Jon LoeligerによるASCIIアートを以下に示します。これは、さまざまなdiff-*コマンドがどのように物を比較するかを示しています。

            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フラグを指定することもできます。これにより、コミットメッセージ、作成者、コミットの日付も表示され、一連のdiffを表示するように指示できます。あるいは、「サイレント」に指示して、diffをまったく表示せずに、実際のコミットメッセージだけを表示することもできます。

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

バージョンのタグ付け

Gitには、「軽量タグ」と「アノテーション付きタグ」の2種類のタグがあります。

「軽量タグ」は、技術的にはブランチと変わりません。.git/refs/tags/サブディレクトリに配置する点が異なります。headとは呼びません。そのため、タグの最も単純な形式は、以下のようになります。

$ git tag my-first-tag

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

$ git diff my-first-tag

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

「アノテーション付きタグ」は、実際には本物の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点目は、マシン間でも当てはまります。`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 reset`で実装していましたが、`git status`や`git commit`など、基本的なGitコマンドを中心としたやや複雑なスクリプトもあります。

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

このような「raw」Gitリポジトリのローカルライブコピーを作成するには、最初にプロジェクトのサブディレクトリを作成し、rawリポジトリの内容を`.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

これにより、上記すべてが実行されます。

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

新しいブランチの作成

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つのブランチのマージ

ブランチを使用するアイデアの1つは、(実験的な可能性のある)作業を行い、最終的にメインブランチにマージすることです。そこで、元の`master`ブランチと同じ状態から開始した上記の`mybranch`を作成したと仮定して、そのブランチに入り、そこで作業してみましょう。

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

ここでは、`hello`に別の行を追加しました。`git update-index hello`と`git commit`の両方を、`-i`フラグ付きで`git commit`にファイル名を直接渡すショートハンドを使用しました(コミットを行う際に、インデックスファイルに対して行った操作に加えて、そのファイルを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

`master`ブランチの方が明らかに気分が良いからです。

これで、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環境で作業するわけではない場合、もう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` ブランチに含まれるコミットを示し、2列目は `mybranch` ブランチを示すために使用されます。3つのコミットとそのタイトルが表示されます。すべてが最初の列に空白以外の文字を持っています(`*` は現在のブランチの通常のコミットを示し、`-` はマージコミットです)。つまり、これらは現在 `master` ブランチの一部です。 "Some work" コミットのみが2列目にプラス `+` 文字を持っています。これは、`mybranch` が `master` ブランチからのこれらのコミットを取り込むためにマージされていないためです。コミットログメッセージの前にあるブラケット内の文字列は、コミットに付ける短い名前です。上記の例では、*master* と *mybranch* はブランチヘッドです。*master^* は *master* ブランチヘッドの最初の親です。より複雑なケースについては、gitrevisions[7] を参照してください。

注記
`--more=1` オプションがない場合、`git show-branch` は *[mybranch]* コミットが *master* と *mybranch* の両方の先端の共通の先祖であるため、*[master^]* コミットを出力しません。詳細については、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` ブランチの先頭に更新しました。これは、多くの場合 *高速転送* マージと呼ばれます。

`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` を使用してリポジトリを準備する必要があります。

リモートリポジトリからフェッチしたら、それを現在のブランチにマージします。

しかし、フェッチしてすぐにマージすることは非常に一般的なことなので、`git pull` と呼ばれ、次のように簡単に実行できます。

$ git pull <remote-repository>

必要に応じて、リモートエンドのブランチ名を2番目の引数として指定できます。

注記
ブランチをまったく使用せずに、ブランチの数だけローカルリポジトリを保持し、ブランチ間と同様に *git pull* でそれらをマージすることで対処できます。このアプローチの利点は、チェックアウトされた各 `ブランチ` のファイルセットを保持できることで、複数の開発ラインを同時に処理する場合、前後に切り替えるのが簡単になる場合があります。もちろん、複数の作業ツリーを保持するためにより多くのディスク容量を使用するコストが発生しますが、現在ではディスク容量は安価です。

同じリモートリポジトリから何度もプルする可能性があります。省略記号として、次のようにローカルリポジトリの構成ファイルにリモートリポジトリの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

マージの仕組み

このチュートリアルでは、フラッシュしない磁器に対処するために配管がどのように機能するかを示しますが、これまでのところ、マージが実際にどのように機能するかについては説明していません。初めてこのチュートリアルに従う場合は、「作業の公開」セクションにスキップして、後でここに戻ってくることをお勧めします。

了解しましたか?見てみるための例として、前の "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

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

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

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

コマンドは共通の祖先コミットのコミットオブジェクト名を標準出力に書き込むため、次のステップで使用するため、その出力を変数にキャプチャしました。ちなみに、この場合、共通の祖先コミットは "Initial 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に *圧縮* されます。また、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* のみが圧縮されました。しかし、実際の大きなプロジェクトでは、コミットで変更されるファイルがごくわずかしかない場合、この *圧縮* はほとんどのパスを非常に迅速に自明にマージし、ゼロ以外のステージには少数の実際の変更のみを残します。

ゼロ以外のステージのみを表示するには、`--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つのバージョンを記述するパラメーターで呼び出され、マージの結果を作業ツリーに残す責任があります。これはかなり単純なシェルスクリプトであり、最終的にはファイルレベルの3方向マージを実行するために RCS スイートの *merge* プログラムを呼び出します。この場合、*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 リポジトリの管理方法です。

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

まず、リモートマシン上に、公開リポジトリを格納する空のリポジトリを作成する必要があります。この空のリポジトリは、後でプッシュすることで内容が追加され、最新の状態に維持されます。明らかに、このリポジトリの作成は一度だけ行う必要があります。

注記
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

このディレクトリが、変更をプルしたい他のユーザーが選択したトランスポートを介してアクセスできるようにしてください。また、$PATHgit-receive-packプログラムがあることを確認する必要があります。

注記
多くのsshdのインストールでは、プログラムを直接実行する場合、ログインシェルとしてシェルを呼び出しません。つまり、ログインシェルがbashの場合、.bashrcのみが読み込まれ、.bash_profileは読み込まれません。回避策として、git-receive-packプログラムを実行できるように、.bashrc$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を再度実行すると、「パックする新しいものはありません。」と表示されます。開発を続け、変更が蓄積されたら、git repackを再度実行すると、最後にリポジトリをパックして以降に作成されたオブジェクトを含む新しいパックが作成されます。プロジェクトの初期インポート直後にプロジェクトをパックすることをお勧めします(プロジェクトを最初から始める場合を除く)。その後、プロジェクトの活動状況に応じて、適宜git repackを実行します。

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

他の人と協力して作業する

Gitは真に分散型システムですが、開発者の非公式な階層でプロジェクトを編成する方が便利な場合があります。Linuxカーネル開発はこの方法で行われています。Randy Dunlap氏のプレゼンテーション(17ページ、「メインラインへのマージ」)に優れた図解があります。

この階層は完全に非公式であることを強調しておく必要があります。この階層が暗示する「パッチフローの連鎖」を強制するGitの基本的なものはありません。単一のリモートリポジトリからのみプルする必要はありません。

「プロジェクトリーダー」には、次のワークフローをお勧めします。

  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ブランチを取得するには、それを`master~2`にリセットする必要があります。

$ git reset --hard master~2

git show-branchが、先ほど行った2つの`git merge`以前の状態と一致することを確認できます。次に、2つの`git merge`コマンドを連続して実行する代わりに、これら2つのブランチヘッドをマージします(これは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つずつマージし、競合の解決方法と、一方の変更を他方よりも優先した理由を文書化する必要があります。そうでなければ、プロジェクトの履歴が分かりにくくなり、分かりやすくなるどころではありません。

Git

git[1]スイートの一部

scroll-to-top