セットアップと設定
プロジェクトの取得と作成
基本的なスナップショット作成
ブランチとマージ
プロジェクトの共有と更新
検査と比較
パッチ適用
デバッグ
メール
外部システム
サーバー管理
ガイド
- gitattributes
- コマンドラインインターフェースの慣習
- Git 日常利用
- よくある質問 (FAQ)
- 用語集
- フック
- gitignore
- gitmodules
- リビジョン
- サブモジュール
- チュートリアル
- ワークフロー
- すべてのガイド...
管理
プラミングコマンド
- 2.43.2 → 2.49.0 変更なし
-
2.43.1
2024-02-09
- 2.25.2 → 2.43.0 変更なし
-
2.25.1
2020-02-17
- 2.23.1 → 2.25.0 変更なし
-
2.23.0
2019-08-16
- 2.16.6 → 2.22.5 変更なし
-
2.15.4
2019-12-06
- 2.14.6 変更なし
-
2.13.7
2018-05-22
-
2.12.5
2017-09-22
-
2.11.4
2017-09-22
- 2.10.5 変更なし
-
2.9.5
2017-07-30
-
2.8.6
2017-07-30
- 2.5.6 → 2.7.6 変更なし
-
2.4.12
2017-05-05
- 2.3.10 変更なし
-
2.2.3
2015-09-04
- 2.1.4 変更なし
-
2.0.5
2014-12-17
説明
このチュートリアルでは、「コア」Gitコマンドを使用してGitリポジトリを設定し、作業する方法について説明します。
Gitを単にリビジョン管理システムとして使用するだけであれば、「Git入門チュートリアル」(gittutorial[7]) または Gitユーザーマニュアル から始めることをお勧めします。
しかし、これらの低レベルツールの理解は、Gitの内部構造を理解したい場合に役立ちます。
Gitのコア部分はしばしば「配管 (plumbing)」と呼ばれ、その上にあるより使いやすいユーザーインターフェースは「陶器 (porcelain)」と呼ばれます。配管を直接使用することはあまりないかもしれませんが、陶器がうまく機能しないときに配管が何をしているのかを知っておくと良いでしょう。
このドキュメントが最初に書かれた頃は、多くのポーセレンコマンドはシェルスクリプトでした。簡潔にするため、ここではまだそれらを例として使用し、配管がどのように組み合わされてポーセレンコマンドを形成するかを説明します。ソースツリーには、これらのスクリプトの一部が参考のために `contrib/examples/` に含まれています。これらはもはやシェルスクリプトとして実装されていませんが、配管レイヤーコマンドの動作に関する説明は依然として有効です。
注記
|
より深い技術的な詳細は「注記」としてマークされていることが多く、最初の読解時にはスキップできます。 |
Gitリポジトリの作成
新しいGitリポジトリの作成はこれ以上ないほど簡単です。すべてのGitリポジトリは空の状態から始まり、作業ツリーとして使用したいサブディレクトリを見つけるだけです。これは完全に新しいプロジェクトのための空のディレクトリでも、Gitにインポートしたい既存の作業ツリーでも構いません。
最初の例として、既存のファイルがない状態で、まったく新しいリポジトリを一から作成します。これを git-tutorial と名付けます。開始するには、そのためのサブディレクトリを作成し、そのサブディレクトリに移動し、git init でGitインフラストラクチャを初期化します。
$ mkdir git-tutorial $ cd git-tutorial $ git init
するとGitは次のように応答します
Initialized empty Git repository in .git/
これは、Gitが「特に変わったことはしていない」ということを示しており、新しいプロジェクト用にローカルの `.git` ディレクトリが設定されたことを意味します。これで `.git` ディレクトリが作成され、ls で確認できます。新しい空のプロジェクトの場合、とりわけ次の3つのエントリーが表示されるはずです。
-
`HEAD` という名前のファイルがあり、その中に `ref: refs/heads/master` が記述されています。これはシンボリックリンクに似ており、`HEAD` ファイルを基準として `refs/heads/master` を指しています。
`HEAD` リンクが指すファイルがまだ存在しないことを心配する必要はありません。`HEAD` 開発ブランチを開始するコミットはまだ作成されていないからです。
-
`objects` という名前のサブディレクトリがあり、プロジェクトのすべてのオブジェクトが含まれます。オブジェクトを直接見る実際の必要性はまずありませんが、これらのオブジェクトがリポジトリ内のすべての実際の データ を含んでいることを知っておくと良いかもしれません。
-
`refs` という名前のサブディレクトリがあり、オブジェクトへの参照が含まれます。
特に、`refs` サブディレクトリには `heads` と `tags` という2つのサブディレクトリが含まれます。これらはその名前が示す通り、任意の数の異なる開発の ヘッド (別名 ブランチ) への参照、およびリポジトリ内で特定のバージョンに名前を付けるために作成した任意の タグ への参照を含んでいます。
一点注意点として、特別な `master` ヘッドはデフォルトブランチであり、そのため `.git/HEAD` ファイルはまだ存在しないにもかかわらずそれを指すように作成されました。基本的には、`HEAD` リンクは常に現在作業中のブランチを指すことになっており、常に `master` ブランチで作業することを想定して開始します。
しかし、これは単なる慣習であり、ブランチには好きな名前を付けることができ、`master` ブランチを 持つ 必要さえありません。ただし、一部のGitツールは `.git/HEAD` が有効であると仮定します。
注記
|
オブジェクト は160ビットのSHA-1ハッシュ、別名 オブジェクト名 によって識別され、オブジェクトへの参照は常にそのSHA-1名の40バイトの16進数表現です。`refs` サブディレクトリ内のファイルはこれらの16進数参照(通常は最後に `\n` が付く)を含むと予想され、実際にツリーを構築し始めると、これらの `refs` サブディレクトリ内にこれらの参照を含む41バイトのファイルが多数表示されるはずです。 |
注記
|
上級ユーザーは、このチュートリアルを終えた後で gitrepository-layout[5] を参照することをお勧めします。 |
これで最初のGitリポジトリが作成されました。もちろん、空のままではあまり役に立たないので、データを入れてみましょう。
Gitリポジトリへのデータ投入
これをシンプルかつわかりやすくするために、まずはいくつかの簡単なファイルを投入して、その感触をつかむことから始めましょう。
まず、Gitリポジトリで管理したい任意のファイルをいくつか作成することから始めます。動作の感覚をつかむために、いくつかの「良くない」例から始めます。
$ echo "Hello World" >hello $ echo "Silly example" >example
これで作業ツリー(別名 ワーキングディレクトリ)に2つのファイルが作成されましたが、実際の作業をチェックインするには、2つの手順を踏む必要があります。
-
作業ツリーの状態に関する情報で インデックス ファイル(別名 キャッシュ)を埋める。
-
そのインデックスファイルをオブジェクトとしてコミットする。
最初のステップは簡単です。作業ツリーへの変更をGitに伝えたいときは、git update-index プログラムを使用します。このプログラムは通常、更新したいファイル名のリストを受け取りますが、些細なミスを防ぐため、`--add` フラグで新しいエントリーを追加すること(または `--remove` フラグで既存のエントリーを削除すること)を明示的に指示しない限り、インデックスに新しいエントリーを追加したり(または既存のエントリーを削除したり)することを拒否します。
したがって、作成したばかりの2つのファイルをインデックスに投入するには、次のようにします。
$ git update-index --add hello example
これでGitにこれら2つのファイルを追跡するように指示しました。
実際、そのようにすると、オブジェクトディレクトリを見ると、Gitがオブジェクトデータベースに2つの新しいオブジェクトを追加していることに気づくでしょう。上記のステップを正確に実行した場合、今すぐ次のようにできるはずです。
$ ls .git/objects/??/*
すると2つのファイルが表示されます。
.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238 .git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962
これらはそれぞれ `557db...` と `f24c7...` という名前のオブジェクトに対応しています。
必要であれば、git cat-file を使ってそれらのオブジェクトを見ることができますが、オブジェクトのファイル名ではなく、オブジェクト名を使用する必要があります。
$ git cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238
ここで `-t` は git cat-file にオブジェクトの「タイプ」を伝えるように指示します。Gitは「blob」オブジェクト(つまり、通常のファイル)があることを示し、次のコマンドで内容を見ることができます。
$ git cat-file blob 557db03
これにより「Hello World」が出力されます。オブジェクト `557db03` は、ファイル `hello` の内容に他なりません。
注記
|
そのオブジェクトと `hello` ファイル自体を混同しないでください。オブジェクトとは文字通りそのファイルの特定の 内容 であり、後で `hello` ファイルの内容をどれだけ変更しても、いま見たオブジェクトは決して変更されません。オブジェクトは不変です。 |
注記
|
2番目の例は、ほとんどの場合、オブジェクト名を最初の数桁の16進数に省略できることを示しています。 |
とにかく、前述したように、通常はオブジェクト自体を実際に見ることはなく、40文字もの長い16進数名をタイプすることは通常やりたいことではありません。上記の余談は、git update-index が魔法のようなことを行い、ファイルの内容を実際にGitオブジェクトデータベースに保存したことを示すためだけにありました。
インデックスを更新すると、別のことも行われました。`.git/index` ファイルが作成されたのです。これは現在の作業ツリーを記述するインデックスであり、非常に意識すべきものです。繰り返しになりますが、通常はインデックスファイル自体を心配することはありませんが、これまでのところファイルをGitに実際に「チェックイン」したわけではなく、単にGitにそれらについて 伝えた だけであるという事実に注意してください。
しかし、Gitはそれらを認識しているため、ファイルを操作したり、そのステータスを確認したりするために、最も基本的なGitコマンドの一部を使用できるようになりました。
特に、まだその2つのファイルをGitにチェックインせずに、まず `hello` に別の行を追加することから始めましょう。
$ echo "It's a new day for git" >>hello
これで、`hello` の以前の状態をGitに伝えたので、git diff-files コマンドを使用して、古いインデックスと比較してツリーで何が変更されたかをGitに尋ねることができます。
$ git diff-files
おっと。これはあまり読みやすくありませんでした。それは単に独自の内部バージョンの diff を吐き出しただけですが、その内部バージョンは単に「hello」が変更されたこと、そして以前のオブジェクトの内容が別のものに置き換えられたことを示しているだけです。
読みやすくするために、`-p` フラグを使用して、git diff-files に差分をパッチとして出力するように指示できます。
$ git diff-files -p diff --git a/hello b/hello index 557db03..263414f 100644 --- a/hello +++ b/hello @@ -1 +1,2 @@ Hello World +It's a new day for git
つまり、`hello` に別の行を追加することで生じた変更の差分です。
言い換えれば、git diff-files は常にインデックスに記録されているものと、現在の作業ツリーにあるものの違いを示します。これは非常に便利です。
`git diff-files -p` の一般的な短縮形は、単に `git diff` と書くことです。これも同じことを行います。
$ git diff diff --git a/hello b/hello index 557db03..263414f 100644 --- a/hello +++ b/hello @@ -1 +1,2 @@ Hello World +It's a new day for git
Gitの状態をコミットする
さて、Gitの次の段階に進みましょう。それは、Gitがインデックス内で認識しているファイルを実際のツリーとしてコミットすることです。これには2つのフェーズがあります。ツリー オブジェクトを作成し、その ツリー オブジェクトを コミット オブジェクトとして、そのツリーが何であるかの説明と、その状態に至った経緯に関する情報と共にコミットします。
ツリーオブジェクトの作成は簡単で、git write-tree を使用して行います。オプションや他の入力はありません。`git write-tree` は現在のインデックスの状態を取得し、そのインデックス全体を記述するオブジェクトを書き込みます。言い換えれば、これで異なるすべてのファイル名とその内容(およびパーミッション)をまとめ、Gitの「ディレクトリ」オブジェクトに相当するものを作成していることになります。
$ git write-tree
そして、これは結果のツリーの名前を出力するだけです。この場合(私が説明した通りに実行していれば)次のようになるはずです。
8988da15d077d4829fc51d8544c097def6644dbb
これはまた別の理解しにくいオブジェクト名です。繰り返しになりますが、必要であれば `git cat-file -t 8988d...` を使用して、今度はオブジェクトが「blob」オブジェクトではなく「tree」オブジェクトであることを確認できます(`git cat-file` を使用して生のオブジェクト内容を実際に出力することもできますが、主にバイナリの乱雑な表示になるため、あまり面白くありません)。
しかし、通常は git write-tree を単独で使用することはありません。なぜなら、通常は常に git commit-tree コマンドを使用してツリーをコミットオブジェクトにコミットするからです。実際、git write-tree を単独でまったく使用せず、その結果を git commit-tree の引数として渡す方が簡単です。
git commit-tree は通常、いくつかの引数を取ります。コミットの 親 が何であったかを知りたがりますが、この新しいリポジトリではこれが最初のコミットであり、親がないため、ツリーのオブジェクト名だけを渡せばよいです。ただし、git commit-tree は標準入力からコミットメッセージも受け取りたがり、結果として生成されるコミットのオブジェクト名を標準出力に書き出します。
そして、ここで `HEAD` が指す `.git/refs/heads/master` ファイルを作成します。このファイルはmasterブランチのツリーの先端への参照を含むことになっており、それはまさに git commit-tree が出力するものであるため、これらすべてを一連のシンプルなシェルコマンドで実行できます。
$ tree=$(git write-tree) $ commit=$(echo 'Initial commit' | git commit-tree $tree) $ git update-ref HEAD $commit
この場合、これは他の何とも関連しないまったく新しいコミットを作成します。通常、これはプロジェクトで一度だけ行われ、それ以降のすべてのコミットは以前のコミットの親の上に置かれます。
繰り返しますが、通常はこれを手動で行うことはありません。`git commit` という便利なスクリプトがあり、これらすべてを自動で行ってくれます。したがって、代わりに `git commit` と書くだけで、上記の魔法のようなスクリプト処理を自動で実行してくれたでしょう。
変更を加える
`hello` ファイルに対して git update-index を実行し、その後 `hello` を変更し、新しい `hello` の状態をインデックスファイルに保存した状態と比較できたことを覚えていますか?
さらに、git write-tree が インデックス ファイルの内容をツリーに書き込むと述べたことを覚えていますか?したがって、私たちがコミットしたものは、実際には新しい内容ではなく、`hello` ファイルの 元の 内容でした。これは意図的に行ったことで、インデックスの状態と作業ツリーの状態の間の違い、そしてコミットする際にもそれらが一致する必要がないことを示すためです。
以前と同様に、`git-tutorial` プロジェクトで `git diff-files -p` を実行すると、前回と同じ差分が表示されます。インデックスファイルは何もコミットしたことで変更されていません。しかし、何かをコミットした今、新しいコマンド git diff-index の使用法も学ぶことができます。
インデックスファイルと作業ツリーの差分を表示する git diff-files とは異なり、git diff-index はコミットされた ツリー とインデックスファイルまたは作業ツリーの差分を表示します。言い換えれば、git diff-index は比較対象のツリーを必要とし、コミットを行う前は比較対象がなかったため、それができませんでした。
しかし、今は実行できます。
$ git diff-index -p HEAD
(`-p` は git diff-files と同じ意味を持ちます)すると、同じ差分が表示されますが、まったく異なる理由からです。今度は、作業ツリーをインデックスファイルに対してではなく、書き込んだばかりのツリーに対して比較しています。たまたまこれら2つは明らかに同じなので、同じ結果が得られます。
繰り返しになりますが、これはよくある操作なので、次のように短縮することもできます。
$ git diff HEAD
これにより、上記が自動的に行われます。
言い換えれば、git diff-index は通常、ツリーを作業ツリーと比較しますが、`--cached` フラグが指定された場合、代わりにインデックスキャッシュの内容のみと比較し、現在の作業ツリーの状態を完全に無視するように指示されます。インデックスファイルをHEADに書き込んだばかりなので、`git diff-index --cached -p HEAD` を実行すると、空の差分セットが返されるはずであり、実際にそうなります。
注記
|
git diff-index は常にインデックスを比較に使用するため、ツリーを作業ツリーと比較すると言うのは厳密には正確ではありません。特に、比較対象のファイルリスト(「メタデータ」)は、`--cached` フラグが使用されているかどうかにかかわらず、常に インデックスファイルから取得されます。`--cached` フラグは、比較対象のファイル 内容 が作業ツリーから来るかどうかを決定するだけです。 Gitが明示的に指示されていないファイルについて決して認識しない(あるいは気にしない)ということを理解すれば、これは難しくありません。Gitは比較するファイルを 探しに行く ことはなく、どのファイルであるかをあなたが指示することを期待しており、そのためにインデックスが存在するのです。 |
さて、次のステップは、私たちが行った 変更 をコミットすることです。何が起こっているかを理解するため、ここでも「作業ツリーの内容」、「インデックスファイル」、「コミットされたツリー」の違いを覚えておいてください。コミットしたい作業ツリー内の変更があり、常にインデックスファイルを介して作業する必要があるため、最初に行うべきことはインデックスキャッシュを更新することです。
$ git update-index hello
(今回は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` が実際に何をするのかに興味があれば、自由に調べてみてください。それは、役立つ(?)コミットメッセージヘッダを生成するためのいくつかの非常にシンプルなシェルスクリプトと、実際にコミット自体を行ういくつかのワンライナー(git commit)です。
変更の検査
変更を作成することは役立ちますが、後で何が変更されたかを判断できればさらに役立ちます。これに最も役立つコマンドは、diff ファミリーの別のコマンド、すなわち git diff-tree です。
git diff-tree には任意の2つのツリーを与えることができ、それらの間の差分を表示します。しかし、おそらくもっと一般的には、単一のコミットオブジェクトを与えるだけで、そのコミットの親を自動的に特定し、直接差分を表示します。したがって、すでに何度か見たのと同じ差分を得るには、次のように実行できます。
$ 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` フラグを与えることができることです。これにより、コミットメッセージ、作者、コミット日時も表示され、一連の差分全体を表示するように指示できます。あるいは、「サイレント」にして、差分をまったく表示せず、実際のコミットメッセージだけを表示するように指示することもできます。
実際、git rev-list プログラム(リビジョンリストを生成する)と組み合わせると、git diff-tree はまさに変更の宝庫となります。`git log` や `git log -p` などを、`git rev-list` の出力を `git diff-tree --stdin` にパイプする簡単なスクリプトでエミュレートできます。これは、`git log` の初期バージョンが実装された方法とまったく同じです。
バージョンのタグ付け
Gitには、「軽量 (light)」タグと「注釈付き (annotated)」タグの2種類のタグがあります。
「軽量」タグは技術的にはブランチに過ぎません。ただし、`head` と呼ぶ代わりに `.git/refs/tags/` サブディレクトリに配置します。したがって、最もシンプルな形式のタグは、単に次のことだけを行います。
$ git tag my-first-tag
これにより、現在の `HEAD` が `.git/refs/tags/my-first-tag` ファイルに書き込まれます。その後、このシンボリック名を使ってその特定の状態を参照できます。例えば、次のようにすることができます。
$ git diff my-first-tag
これにより、現在の状態とそのタグを比較します。この時点では明らかに差分は空ですが、開発を続けてコミットすれば、タグを「アンカーポイント」として使用して、タグ付け以降に何が変更されたかを確認できます。
「注釈付きタグ」は実際には本物のGitオブジェクトであり、タグ付けしたい状態へのポインタだけでなく、短いタグ名とメッセージ、さらにオプションで、あなたが確かにそのタグを作成したことを示すPGP署名を含みます。これらの注釈付きタグは、git tag に `-a` または `-s` フラグを付けて作成します。
$ git tag -s <tagname>
これにより現在の `HEAD` が署名されます(ただし、タグ付けする対象を指定する別の引数を与えることもできます。例えば、`git tag
通常、署名付きタグはメジャーリリースなどのためにのみ行いますが、軽量タグはどのようなマーキングにも役立ちます。特定の時点を記憶しておきたいと決めたらいつでも、そのためのプライベートタグを作成するだけで、その時点の状態に素敵なシンボリック名が付けられます。
リポジトリのコピー
Gitリポジトリは通常、完全に自己完結型で移動可能です。例えばCVSとは異なり、「リポジトリ」と「作業ツリー」という分離された概念はありません。Gitリポジトリは通常、`.git` サブディレクトリにローカルのGit情報が隠された作業ツリー そのもの です。他に何もありません。見たものがすべてです。
注記
|
GitにGitの内部情報を追跡しているディレクトリから分離するように指示することはできますが、今はそれを無視します。これは通常のプロジェクトの動作方法ではなく、実際には特別な用途のためだけです。したがって、「Git情報は常にそれが記述する作業ツリーに直接結びついている」という精神的なモデルは、技術的には100%正確ではないかもしれませんが、すべての通常の用途には良いモデルです。 |
これには2つの意味があります。
-
作成したチュートリアルリポジトリに飽きた場合(または間違いを犯して最初からやり直したい場合)は、簡単に次のことができます。
$ rm -rf git-tutorial
すると消去されます。外部リポジトリはなく、作成したプロジェクトの外部に履歴はありません。
-
Gitリポジトリを移動または複製したい場合、そうすることができます。git clone コマンドがありますが、リポジトリのコピー(すべての完全な履歴を含む)を作成したいだけであれば、通常の `cp -a git-tutorial new-git-tutorial` でそれを行うことができます。
Gitリポジトリを移動またはコピーした場合、Gitのインデックスファイル(関連するファイルの「stat」情報など、さまざまな情報をキャッシュする)はリフレッシュする必要がある可能性が高いことに注意してください。したがって、`cp -a` で新しいコピーを作成した後には、次のようにすることをお勧めします。
$ git update-index --refresh
新しいリポジトリでインデックスファイルが最新であることを確認します。
2つ目の点は、マシン間でも当てはまることに注意してください。リモートGitリポジトリは、scp、rsync、wget のいずれであっても、任意 の通常のコピーメカニズムで複製できます。
リモートリポジトリをコピーする際には、最低限インデックスキャッシュを更新したくなります。特に他人のリポジトリでは、インデックスキャッシュが既知の状態にあることを確認したいことがよくあります(彼らが 何 を行い、まだチェックインしていないのかわからないため)、そのため通常は git update-index の前に次のコマンドを実行します。
$ git read-tree --reset HEAD $ git update-index --refresh
これは、`HEAD` が指すツリーからインデックスを完全に再構築します。インデックスの内容を `HEAD` にリセットし、その後 git update-index がすべてのインデックスエントリーがチェックアウトされたファイルと一致することを確認します。元のリポジトリの作業ツリーに未コミットの変更があった場合、`git update-index --refresh` はそれらを検出し、更新が必要であることを通知します。
上記は単に次のように書くこともできます。
$ git reset
実際、多くの一般的なGitコマンドの組み合わせは、`git xyz` インターフェースでスクリプト化できます。さまざまなGitスクリプトが何をするかを見るだけで、学ぶことができます。例えば、`git reset` はかつて git reset で上記の2行が実装されていましたが、git status や git commit のような一部のものは、基本的なGitコマンドを巡るもう少し複雑なスクリプトです。
多くの(ほとんどの?)公開リモートリポジトリには、チェックアウトされたファイルやインデックスファイルさえ含まれておらず、実際のコアGitファイル のみ が含まれています。そのようなリポジトリは通常、`.git` サブディレクトリさえ持たず、すべてのGitファイルがリポジトリ内に直接存在します。
そのような「生」のGitリポジトリのローカルライブコピーを作成するには、まずプロジェクト用に独自のサブディレクトリを作成し、次に生のGitリポジトリの内容を `.git` ディレクトリにコピーします。例えば、Gitリポジトリの独自のコピーを作成するには、次のようにします。
$ mkdir my-git $ cd my-git $ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git
続いて
$ git read-tree HEAD
インデックスにデータを投入します。しかし、これでインデックスにデータが投入され、Gitの内部ファイルはすべて揃っていますが、作業するための作業ツリーファイルが実際には何もないことに気づくでしょう。それらを取得するには、次のようにチェックアウトします。
$ git checkout-index -u -a
ここで、`-u` フラグはチェックアウトがインデックスを最新の状態に保つことを意味し(後で更新する必要がないように)、`-a` フラグは「すべてのファイルをチェックアウトする」ことを意味します(もし古いコピーまたは以前のバージョンのチェックアウトされたツリーがある場合、git checkout-index に古いファイルの 強制 上書きを指示するために、まず `-f` フラグを追加する必要があるかもしれません)。
繰り返しになりますが、これはすべて次のように簡略化できます。
$ git clone git://git.kernel.org/pub/scm/git/git.git/ my-git $ cd my-git $ git checkout
これにより、上記すべてが自動的に行われます。
これで、他人の(私の)リモートリポジトリを正常にコピーし、チェックアウトしました。
新しいブランチの作成
Gitにおけるブランチは、実際には `.git/refs/` サブディレクトリ内のGitオブジェクトデータベースへのポインタに過ぎず、すでに説明したように、`HEAD` ブランチはこれらのオブジェクトポインタの1つへのシンボリックリンクにすぎません。
プロジェクト履歴内の任意の時点を選び、そのオブジェクトのSHA-1名を `.git/refs/heads/` 以下のファイルに書き込むだけで、いつでも新しいブランチを作成できます。好きなファイル名(および実際にはサブディレクトリ)を使用できますが、慣例として「通常の」ブランチは `master` と呼ばれます。ただし、これは単なる慣例であり、強制されるものではありません。
例としてそれを示すために、以前使用したgit-tutorialリポジトリに戻り、そこにブランチを作成しましょう。それには、単に新しいブランチをチェックアウトしたいと宣言するだけです。
$ git switch -c mybranch
は、現在の `HEAD` の位置に基づいて新しいブランチを作成し、それに切り替えます。
注記
|
現在の `HEAD` 以外の履歴のどこか別の地点で新しいブランチを開始すると決めた場合、git switch にチェックアウトの基点となるものを伝えるだけでそうできます。言い換えれば、以前のタグやブランチがある場合、次のようにするだけです。 $ git switch -c mybranch earlier-commit これにより、`mybranch` という新しいブランチが以前のコミットで作成され、その時点の状態がチェックアウトされます。 |
元の `master` ブランチには、いつでも次のようにして戻ることができます。
$ git switch master
(または他のブランチ名でも構いません)そして、どのブランチにいるのか忘れてしまった場合は、簡単な次のコマンドで確認できます。
$ cat .git/HEAD
は、それがどこを指しているかを教えてくれます。持っているブランチのリストを取得するには、次のように言えます。
$ git branch
これはかつて `ls .git/refs/heads` の周りのシンプルなスクリプトに過ぎませんでした。現在いるブランチの前にアスタリスクが表示されます。
時には、実際にチェックアウトして切り替えることなく、新しいブランチを作成したい場合があります。その場合は、次のコマンドを使用するだけです。
$ git branch <branchname> [startingpoint]
これにより、ブランチは単に 作成 されますが、それ以上は何も行いません。その後、実際にそのブランチで開発したいと決めたら、引数としてブランチ名を持つ通常の git switch でそのブランチに切り替えることができます。
2つのブランチをマージする
ブランチを持つことの目的の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
マスターブランチの方が明らかにずっと機嫌が良いので。
さて、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 と mybranch はブランチヘッドです。master^ は master ブランチヘッドの最初の親です。より複雑なケースを見たい場合は、gitrevisions[7] を参照してください。
注記
|
--more=1 オプションがない場合、*[mybranch]* コミットは master と mybranch 両方の先端の共通の祖先であるため、git show-branch は *[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` ブランチのものに更新しただけです。これはしばしば fast-forward (早送り) マージと呼ばれます。
もう一度 `gitk --all` を実行してコミットの祖先がどのように見えるかを確認したり、このことを教えてくれる show-branch を実行したりできます。
$ git show-branch master mybranch ! [master] Merge work in mybranch * [mybranch] Merge work in mybranch -- -- [master] Merge work in mybranch
外部作業のマージ
自身のブランチをマージするよりも、他者とマージすることの方がはるかに一般的です。そのため、Gitがそれを非常に簡単にするという点に注目する価値があります。実際、git merge を行うのとそれほど違いはありません。実際、リモートマージは「リモートリポジトリから一時タグに作業をフェッチする」ことの後に git merge を実行することに他なりません。
リモートリポジトリからのフェッチは、当然のことながら、git fetch によって行われます。
$ git fetch <remote-repository>
ダウンロード元リポジトリの指定には、以下のいずれかのトランスポートを使用できます。
- SSH
-
`remote.machine:/path/to/repo.git/` または
ssh://remote.machine/path/to/repo.git/
このトランスポートはアップロードとダウンロードの両方に使用でき、リモートマシンへの `ssh` 経由のログイン権限が必要です。両端が持つヘッドコミットを交換することで、相手側が不足しているオブジェクトのセットを特定し、最小限のオブジェクトセット(に近いもの)を転送します。これはリポジトリ間でGitオブジェクトを交換する最も効率的な方法です。
- ローカルディレクトリ
-
/path/to/repo.git/
このトランスポートはSSHトランスポートと同じですが、ssh 経由でリモートマシンで他方の端を実行する代わりに、ローカルマシンで両方の端を実行するために sh を使用します。
- Gitネイティブ
-
git://remote.machine/path/to/repo.git/
このトランスポートは匿名ダウンロード用に設計されました。SSHトランスポートと同様に、ダウンストリーム側が不足しているオブジェクトのセットを特定し、最小限のオブジェクトセット(に近いもの)を転送します。
- HTTP(S)
-
http://remote.machine/path/to/repo.git/
HTTPおよびHTTPS URLからのダウンローダーは、まず `repo.git/refs/` ディレクトリの下にある指定された参照名を見て、リモートサイトから最上位のコミットオブジェクト名を取得し、次にそのコミットオブジェクトのオブジェクト名を使用して `repo.git/objects/xx/xxx...` からダウンロードしてコミットオブジェクトを取得しようとします。その後、コミットオブジェクトを読み取ってその親コミットと関連するツリーオブジェクトを特定し、必要なすべてのオブジェクトを取得するまでこのプロセスを繰り返します。この動作のため、これらは「コミットウォーカー」とも呼ばれることがあります。
コミットウォーカー は、GitネイティブトランスポートのようなGitを認識するスマートサーバーを必要としないため、ダムトランスポート とも呼ばれることがあります。ディレクトリインデックスをサポートしていない一般的なHTTPサーバーでも十分です。ただし、ダムトランスポートダウンローダーを補助するために、git update-server-info を使用してリポジトリを準備する必要があります。
リモートリポジトリからフェッチしたら、それを現在のブランチに `merge` します。
しかし、`fetch` してすぐに `merge` するのは非常に一般的なことなので、それは `git pull` と呼ばれ、単に次のように実行できます。
$ git pull <remote-repository>
そして、オプションで2番目の引数としてリモート側のブランチ名を指定します。
注記
|
ブランチをまったく使わずに作業することも可能です。ブランチと同じ数のローカルリポジトリを保持し、git pull でそれらの間をマージするのです。このアプローチの利点は、各 `branch` のファイルセットをチェックアウトしたままにできるため、複数の開発ラインを同時に扱っている場合に、行ったり来たりするのが簡単になることです。もちろん、複数の作業ツリーを保持するためにディスク使用量が増えるという代償はありますが、最近ではディスクスペースは安価です。 |
同じリモートリポジトリから定期的にプルする可能性が高いでしょう。ショートハンドとして、リモートリポジトリのURLをローカルリポジトリの設定ファイルに次のように保存できます。
$ git config remote.linus.url https://git.kernel.org/pub/scm/git/git.git/
そして、完全なURLの代わりに「linus」というキーワードを git pull で使用します。
例。
-
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
マージの仕組み
このチュートリアルでは、ポーセレンが機能しない場合に配管がどのように役立つかを示すと述べましたが、これまでのところマージが実際にどのように機能するかについては触れていません。このチュートリアルを初めて読む場合は、「作業を公開する」セクションにスキップし、後でここに戻ることをお勧めします。
はい、まだ付いてきていますか?例を見てみるために、以前の「hello」と「example」ファイルを含むリポジトリに戻り、マージ前の状態に戻しましょう。
$ git show-branch --more=2 master mybranch ! [master] Merge work in mybranch * [mybranch] Merge work in mybranch -- -- [master] Merge work in mybranch +* [master^2] Some work. +* [master^] Some fun.
覚えておいてください、git merge を実行する前、私たちの `master` ヘッドは「Some fun.」コミットにあり、`mybranch` ヘッドは「Some work.」コミットにありました。
$ git switch -C mybranch master^2 $ git switch master $ git reset --hard master^
巻き戻した後、コミット構造は次のようになります。
$ git show-branch * [master] Some fun. ! [mybranch] Some work. -- * [master] Some fun. + [mybranch] Some work. *+ [master^] Initial commit
これで、マージを手動で試す準備ができました。
`git merge` コマンドは、2つのブランチをマージする際に3-wayマージアルゴリズムを使用します。まず、それらの共通の祖先を見つけます。使用するコマンドは git merge-base です。
$ mb=$(git merge-base HEAD mybranch)
このコマンドは共通の祖先のコミットオブジェクト名を標準出力に書き出すため、次のステップで使用するため出力を変数にキャプチャしました。ちなみに、この場合の共通の祖先コミットは「Initial commit」コミットです。これは次で確認できます。
$ git name-rev --name-only --tags $mb my-first-tag
共通の祖先コミットを見つけたら、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 だけが折りたたまれました。しかし、実際の大きなプロジェクトでは、1つのコミットで少数のファイルしか変更されない場合、この 折りたたみ はほとんどのパスをかなり迅速に自明的にマージし、非ゼロのステージにはほんの一握りの実際の変更しか残さない傾向があります。
非ゼロのステージのみを見るには、`--unmerged` フラグを使用します。
$ git ls-files --unmerged 100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello 100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
マージの次のステップは、3-wayマージを使用して、ファイルのこれら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-wayマージを実行します。この場合、merge は競合を検出し、競合マーク付きのマージ結果が作業ツリーに残されます。これは、この時点で再度 `ls-files --stage` を実行すると確認できます。
$ git ls-files --stage 100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example 100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello 100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
これは、git merge が制御をあなたに戻した後、インデックスファイルと作業ファイルの競合するマージを解決するために残した状態です。パス `hello` がまだマージされていないことに注目してください。この時点で git diff で表示されるのは、ステージ2以降の差分(つまり、あなたのバージョン)です。
作業の公開
さて、他人のリモートリポジトリからの作業を使うことはできますが、あなた が他の人がプルできるようにリポジトリを準備するにはどうすればよいでしょうか?
実際の作業は、主要なリポジトリが `.git` サブディレクトリとしてその下にぶら下がっている作業ツリーで行います。そのリポジトリをリモートからアクセス可能にして、人々にそこからプルするように依頼することも できます が、実際には通常そうはしません。推奨される方法は、公開リポジトリを持ち、それを他の人がアクセスできるようにし、主要な作業ツリーで行った変更が良い状態になったら、それから公開リポジトリを更新することです。これはしばしば プッシュ と呼ばれます。
注記
|
この公開リポジトリはさらにミラーリングされることもあり、`kernel.org` のGitリポジトリはそのように管理されています。 |
ローカル(プライベート)リポジトリからリモート(公開)リポジトリへ変更を公開するには、リモートマシンでの書き込み権限が必要です。そこで単一のコマンド git-receive-pack を実行するために、SSHアカウントが必要です。
まず、リモートマシンに公開リポジトリを格納する空のリポジトリを作成する必要があります。この空のリポジトリは、後でプッシュすることでデータが投入され、最新の状態に保たれます。当然ながら、このリポジトリの作成は一度だけ行えばよいです。
注記
|
git push は、ローカルマシン上の git send-pack とリモートマシン上の git-receive-pack という一対のコマンドを使用します。ネットワークを介した両者間の通信は内部的にSSH接続を使用します。 |
あなたのプライベートリポジトリのGitディレクトリは通常 `.git` ですが、公開リポジトリはプロジェクト名、つまり `<project>.git` にちなんで名付けられることが多いです。プロジェクト `my-git` のための公開リポジトリを作成してみましょう。リモートマシンにログインした後、空のディレクトリを作成します。
$ mkdir my-git.git
次に、そのディレクトリを git init を実行してGitリポジトリにします。ただし、今回はその名前が通常の `.git` ではないため、少し異なる方法で行います。
$ GIT_DIR=my-git.git git init
このディレクトリが、あなたの選択したトランスポート経由で変更をプルしてもらいたい他の人々に利用可能であることを確認してください。また、`$PATH` に git-receive-pack プログラムがあることも確認する必要があります。
注記
|
sshdの多くのインストールでは、プログラムを直接実行する際にログインシェルとしてシェルを呼び出しません。これは、ログインシェルが bash の場合、`.bash_profile` ではなく `.bashrc` のみが読み込まれることを意味します。この問題を回避するには、`.bashrc` が `$PATH` を設定し、git-receive-pack プログラムを実行できるようにしてください。 |
注記
|
このリポジトリを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-*.pack` と `pack-*.idx` の2つのファイルが表示されます。これらは互いに密接に関連しており、何らかの理由でこれらを別のリポジトリに手動でコピーする場合は、必ず一緒にコピーするようにしてください。前者はパック内のオブジェクトのすべてのデータを保持し、後者はランダムアクセス用のインデックスを保持します。 |
偏執的であるならば、git verify-pack コマンドを実行すれば破損したパックがあるかどうかを検出できますが、あまり心配しないでください。私たちのプログラムは常に完璧です ;-)。
オブジェクトをパックしたら、パックファイルに含まれる展開済みオブジェクトをこれ以上残しておく必要はありません。
$ git prune-packed
がそれらを削除してくれます。
興味があれば、`git prune-packed` を実行する前と後に `find .git/objects -type f` を実行してみてください。また、`git count-objects` は、リポジトリに展開されていないオブジェクトがいくつあり、どれくらいのスペースを消費しているかを教えてくれます。
注記
|
パックされたリポジトリでは比較的大きなパックに比較的少数のオブジェクトしか含まれない可能性があるため、HTTPトランスポートでは `git pull` がやや扱いにくい場合があります。公開リポジトリからのHTTPプルが多いと予想される場合は、頻繁にリパック&プルーニングを行うか、まったく行わないことをお勧めします。 |
この時点で再度 `git repack` を実行すると、「Nothing new to pack.(新しくパックするものはありません。)」と表示されます。開発を続け、変更を蓄積すると、再度 `git repack` を実行することで、前回リポジトリをパックしてから作成されたオブジェクトを含む新しいパックが作成されます。最初のインポート後(プロジェクトを一から開始する場合を除く)すぐにプロジェクトをパックし、その後はプロジェクトの活動状況に応じて定期的に `git repack` を実行することをお勧めします。
リポジトリが `git push` と `git pull` を介して同期される際、ソースリポジトリでパックされたオブジェクトは、通常、デスティネーションでは展開された状態で保存されます。これにより、両端で異なるパック戦略を使用できますが、定期的に両方のリポジトリをリパックする必要がある場合があることも意味します。
他者との共同作業
Gitは真に分散型のシステムですが、開発者を非公式な階層で組織化するとプロジェクト管理が便利な場合が多くあります。Linuxカーネルの開発もこの方法で進められています。Randy Dunlap氏のプレゼンテーション には、その好例(17ページ、「Merges to Mainline」)があります。
この階層は純粋に 非公式 であることを強調しておくべきです。Gitには、この階層が暗示する「パッチの流れの連鎖」を強制するような根本的なものは何もありません。1つのリモートリポジトリからのみプルする必要はありません。
「プロジェクトリーダー」向けの推奨ワークフローは次のとおりです。
-
ローカルマシンで主要なリポジトリを準備します。あなたの作業はそこで行われます。
-
他の人がアクセスできる公開リポジトリを準備します。
他の人がダムトランスポートプロトコル(HTTP)を介してあなたのリポジトリからプルしている場合、このリポジトリを ダムトランスポートフレンドリー に保つ必要があります。`git init` 後、標準テンプレートからコピーされた `$GIT_DIR/hooks/post-update.sample` には git update-server-info の呼び出しが含まれますが、`mv post-update.sample post-update` でフックを手動で有効にする必要があります。これにより、git update-server-info が必要なファイルを最新の状態に保つことが保証されます。
-
主要なリポジトリから公開リポジトリにプッシュします。
-
公開リポジトリを git repack します。これにより、初期のオブジェクトセットをベースラインとして含む大きなパックが確立され、あなたのリポジトリからプルするためのトランスポートがパックされたリポジトリをサポートしている場合は、git prune も行われる可能性があります。
-
主要なリポジトリでの作業を続けます。あなたの変更には、あなた自身の修正、電子メールで受け取るパッチ、「サブシステムメンテナー」の「公開」リポジトリからのプルによるマージが含まれます。
このプライベートリポジトリは、好きなときにいつでもリパックできます。
-
変更を公開リポジトリにプッシュし、一般に公開します。
-
時々、公開リポジトリを git repack します。ステップ5に戻り、作業を続けます。
そのプロジェクトで作業し、独自の「公開リポジトリ」を持つ「サブシステムメンテナー」向けの推奨作業サイクルは次のとおりです。
-
「プロジェクトリーダー」の公開リポジトリに対して git clone を実行して、作業リポジトリを準備します。最初のクローン作成に使用されたURLは、`remote.origin.url` 設定変数に保存されます。
-
「プロジェクトリーダー」が行うように、他の人がアクセスできる公開リポジトリを準備します。
-
「プロジェクトリーダー」のリポジトリが自分のマシンと同じマシンにある場合を除き、「プロジェクトリーダー」の公開リポジトリからパックされたファイルを自分の公開リポジトリにコピーします。後者の場合、`objects/info/alternates` ファイルを使用して、借用元のリポジトリを指すことができます。
-
主要なリポジトリから公開リポジトリにプッシュします。git repack を実行し、あなたのリポジトリからプルするためのトランスポートがパックされたリポジトリをサポートしている場合は、git prune も行う可能性があります。
-
主要なリポジトリでの作業を続けます。あなたの変更には、あなた自身の修正、電子メールで受け取るパッチ、「プロジェクトリーダー」および場合によっては「サブサブシステムメンテナー」の「公開」リポジトリからのプルによるマージが含まれます。
このプライベートリポジトリは、好きなときにいつでもリパックできます。
-
変更を自身の公開リポジトリにプッシュし、「プロジェクトリーダー」および場合によっては「サブサブシステムメンテナー」にそこからプルするように依頼します。
-
時々、公開リポジトリを git repack します。ステップ5に戻り、作業を続けます。
「公開」リポジトリを持たない「個人開発者」向けの推奨作業サイクルは、やや異なります。次のようになります。
-
「プロジェクトリーダー」(または、サブシステムで作業する場合は「サブシステムメンテナー」)の公開リポジトリを git clone して、作業リポジトリを準備します。最初のクローン作成に使用されたURLは、`remote.origin.url` 設定変数に保存されます。
-
あなたのリポジトリの master ブランチで作業を行います。
-
時々、アップストリームの公開リポジトリから `git fetch origin` を実行します。これは `git pull` の前半部分のみを行い、マージはしません。公開リポジトリのヘッドは `.git/refs/remotes/origin/master` に保存されます。
-
`git cherry origin` を使用して、あなたのパッチのどれが受け入れられたかを確認し、および/または `git rebase origin` を使用して、未マージの変更を更新されたアップストリームに転送します。
-
`git format-patch origin` を使用して、アップストリームへの電子メール提出用のパッチを準備し、送信します。ステップ2に戻り、続けます。
他者との共同作業、共有リポジトリスタイル
CVSの経験がある方にとって、前のセクションで提案された共同作業のスタイルは新しいかもしれません。心配する必要はありません。Gitは、おそらくあなたがより慣れている「共有公開リポジトリ」スタイルの共同作業もサポートしています。
詳細については、gitcvs-migration[7] を参照してください。
作業のバンドル
一度に複数の作業に取り組む可能性が高いでしょう。Gitのブランチを使用すると、これらの多かれ少なかれ独立したタスクを簡単に管理できます。
以前、「楽しさと作業」の例で2つのブランチを使用してブランチがどのように機能するかをすでに見てきました。2つ以上のブランチがある場合も考え方は同じです。あなたが「master」ヘッドから開始し、「master」ブランチに新しいコードがあり、「commit-fix」と「diff-fix」ブランチに2つの独立した修正があるとします。
$ git show-branch ! [commit-fix] Fix commit message normalization. ! [diff-fix] Fix rename detection. * [master] Release candidate #1 --- + [diff-fix] Fix rename detection. + [diff-fix~1] Better common substring algorithm. + [commit-fix] Fix commit message normalization. * [master] Release candidate #1 ++* [diff-fix~2] Pretty-print messages.
両方の修正が十分にテストされ、この時点で両方をマージしたいとします。次のように、まず diff-fix をマージし、次に commit-fix をマージできます。
$ git merge -m "Merge fix in diff-fix" diff-fix $ git merge -m "Merge fix in commit-fix" commit-fix
その結果、次のようになります。
$ git show-branch ! [commit-fix] Fix commit message normalization. ! [diff-fix] Fix rename detection. * [master] Merge fix in commit-fix --- - [master] Merge fix in commit-fix + * [commit-fix] Fix commit message normalization. - [master~1] Merge fix in diff-fix +* [diff-fix] Fix rename detection. +* [diff-fix~1] Better common substring algorithm. * [master~2] Release candidate #1 ++* [master~3] Pretty-print messages.
しかし、あなたが持っているものが真に独立した変更のセットである場合(順序が重要であるならば、定義上それらは独立していません)、一方のブランチを先にマージし、もう一方を次にマージする特別な理由はありません。代わりに、それら2つのブランチを現在のブランチに一度にマージすることができます。まず、今行ったことを元に戻し、最初からやり直しましょう。これら2つのマージの前にマスターブランチの状態にするには、master~2 にリセットします。
$ git reset --hard master~2
実行した2つの git merge の前の状態と `git show-branch` が一致することを確認できます。次に、続けて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.
オクトパス(多重)マージは、できるからといって行うべきではないことに注意してください。オクトパスは有効な行為であり、2つ以上の独立した変更を同時にマージする場合にコミット履歴を視覚的に見やすくすることがよくあります。ただし、マージしようとしているブランチのいずれかでマージ競合が発生し、手動で解決する必要がある場合、それはそれらのブランチで行われた開発が結局独立していなかったことを示す兆候であり、競合をどのように解決したか、そして一方の側の変更を他方よりも優先した理由を文書化しながら、一度に2つずつマージすべきです。そうしないと、プロジェクトの履歴を追跡するのが難しくなり、簡単にはなりません。
Git
git[1] スイートの一部