日本語 ▾ トピック ▾ 最新バージョン ▾ user-manual は 2.48.0 で最終更新

はじめに

Gitは高速な分散型バージョン管理システムです。

このマニュアルは、基本的なUNIXコマンドラインスキルはあるものの、Gitの知識は全くない人でも読めるように設計されています。

リポジトリとブランチおよびGit履歴の探索では、gitを使用してプロジェクトを取得し、調査する方法について説明しています。これらの章を読んで、ソフトウェアプロジェクトの特定のバージョンをビルドおよびテストしたり、リグレッションを検索したりする方法を学んでください。

実際の開発を行う必要がある人は、Gitでの開発および他者との開発の共有も読むことをお勧めします。

さらに、より専門的なトピックを扱う章もあります。

包括的なリファレンスドキュメントは、manページまたはgit-help[1]コマンドで利用できます。たとえば、git clone <repo>コマンドの場合、次のいずれかを使用できます。

$ man git-clone

または

$ git help clone

後者では、お好みのマニュアルビューアを使用できます。詳細については、git-help[1]を参照してください。

説明なしでGitコマンドの簡単な概要を知りたい場合は、Gitクイックリファレンスも参照してください。

最後に、このマニュアルをより完全にするために協力する方法については、このマニュアルの注意事項とToDoリストを参照してください。

リポジトリとブランチ

Gitリポジトリの入手方法

このマニュアルを読みながら実験するために、Gitリポジトリがあると便利です。

入手する最良の方法は、git-clone[1]コマンドを使用して既存のリポジトリのコピーをダウンロードすることです。まだプロジェクトを検討していない場合は、いくつか興味深い例を挙げます。

	# Git itself (approx. 40MB download):
$ git clone git://git.kernel.org/pub/scm/git/git.git
	# the Linux kernel (approx. 640MB download):
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git

大規模なプロジェクトの場合、最初のクローンには時間がかかる場合がありますが、クローンは一度だけ行う必要があります。

クローンコマンドは、プロジェクト名(上記の例ではgitまたはlinux)の新しいディレクトリを作成します。このディレクトリに移動すると、プロジェクトファイルのコピー(ワーキングツリーと呼ばれる)と、プロジェクトの履歴に関するすべての情報を含む.gitという特別なトップレベルディレクトリが含まれていることがわかります。

プロジェクトの異なるバージョンをチェックアウトする方法

Gitは、ファイルのコレクションの履歴を保存するためのツールとして考えるのが最適です。プロジェクトのコンテンツの相互に関連するスナップショットの圧縮されたコレクションとして履歴を保存します。Gitでは、このような各バージョンをコミットと呼びます。

これらのスナップショットは、必ずしもすべてが最も古いものから最も新しいものまで単一の線上に配置されているわけではありません。代わりに、ブランチと呼ばれる並行開発ラインに沿って作業が同時に進行し、マージと分岐が行われる場合があります。

単一のGitリポジトリは、複数のブランチでの開発を追跡できます。これは、各ブランチの最新のコミットを参照するヘッドのリストを保持することで行われます。git-branch[1]コマンドは、ブランチヘッドのリストを表示します。

$ git branch
* master

新しくクローンされたリポジトリには、デフォルトで「master」という名前の単一のブランチヘッドが含まれており、ワーキングディレクトリはそのブランチヘッドが参照するプロジェクトの状態に初期化されています。

ほとんどのプロジェクトではタグも使用されます。タグはヘッドと同様にプロジェクトの履歴への参照であり、git-tag[1]コマンドを使用してリストできます。

$ git tag -l
v2.6.11
v2.6.11-tree
v2.6.12
v2.6.12-rc2
v2.6.12-rc3
v2.6.12-rc4
v2.6.12-rc5
v2.6.12-rc6
v2.6.13
...

タグは常にプロジェクトの同じバージョンを指すことが期待されますが、ヘッドは開発が進むにつれて進むことが期待されます。

これらのバージョンのいずれかを指す新しいブランチヘッドを作成し、git-switch[1]を使用してチェックアウトします。

$ git switch -c new v2.6.13

次に、ワーキングディレクトリは、プロジェクトがv2.6.13としてタグ付けされたときのコンテンツを反映し、git-branch[1]は2つのブランチを表示し、アスタリスクで現在チェックアウトされているブランチをマークします。

$ git branch
  master
* new

バージョン2.6.17を表示したい場合は、現在のブランチをv2.6.17を指すように変更できます。

$ git reset --hard v2.6.17

現在のブランチヘッドが履歴の特定の時点への唯一の参照であった場合、そのブランチをリセットすると、以前に指していた履歴を見つける方法がなくなる可能性があることに注意してください。したがって、このコマンドは慎重に使用してください。

履歴の理解:コミット

プロジェクトの履歴におけるすべての変更はコミットで表されます。git-show[1]コマンドは、現在のブランチの最新のコミットを表示します。

$ git show
commit 17cf781661e6d38f737f15f53ab552f1e95960d7
Author: Linus Torvalds <torvalds@ppc970.osdl.org.(none)>
Date:   Tue Apr 19 14:11:06 2005 -0700

    Remove duplicate getenv(DB_ENVIRONMENT) call

    Noted by Tony Luck.

diff --git a/init-db.c b/init-db.c
index 65898fa..b002dc6 100644
--- a/init-db.c
+++ b/init-db.c
@@ -7,7 +7,7 @@

 int main(int argc, char **argv)
 {
-	char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
+	char *sha1_dir, *path;
 	int len, i;

 	if (mkdir(".git", 0755) < 0) {

ご覧のとおり、コミットは誰が最新の変更を行ったか、何をしたか、そして理由を示します。

すべてのコミットには40桁の16進数IDがあり、「オブジェクト名」または「SHA-1 ID」と呼ばれることもあります。これはgit show出力の最初の行に表示されます。通常、コミットはタグやブランチ名などの短い名前で参照できますが、この長い名前も便利です。最も重要なことは、このコミットのグローバルに一意な名前であるということです。したがって、他の誰かにオブジェクト名(たとえば電子メールで)を伝えた場合、その名前は彼らのリポジトリ(彼らのリポジトリにそのコミットが全くあると仮定して)であなたのリポジトリと同じコミットを参照することが保証されます。オブジェクト名はコミットの内容のハッシュとして計算されるため、コミットは名前も変更することなく変更されることはありません。

実際、Gitの概念では、ファイルデータやディレクトリの内容を含むGit履歴に保存されているすべてのものは、その内容のハッシュである名前を持つオブジェクトに保存されていることがわかります。

履歴の理解:コミット、親、到達可能性

すべてのコミット(プロジェクトの最初のコミットを除く)には、このコミットの前に何が起こったかを示す親コミットもあります。親の連鎖をたどると、最終的にプロジェクトの始まりに戻ることができます。

ただし、コミットは単純なリストを形成しません。Gitは開発ラインが分岐し、その後再収束することを可能にし、2つの開発ラインが再収束する点を「マージ」と呼びます。したがって、マージを表すコミットは、複数の親を持つことができ、各親は、その点に至る開発ラインの1つにおける最新のコミットを表します。

これがどのように機能するかを確認する最良の方法は、gitk[1]コマンドを使用することです。現在Gitリポジトリでgitkを実行し、マージコミットを探すと、Gitが履歴をどのように整理しているかを理解するのに役立ちます。

以下では、コミットXがコミットYの祖先である場合、コミットXはコミットYから「到達可能」であると言います。言い換えれば、YはXの子孫である、またはコミットYからコミットXへの親の連鎖があると言うことができます。

履歴の理解:履歴図

Gitの履歴は、以下のような図で表現することがあります。コミットは「o」で示され、それらの間のリンクは - / および \ で描かれた線で示されます。時間は左から右に進みます。

         o--o--o <-- Branch A
        /
 o--o--o <-- master
        \
         o--o--o <-- Branch B

特定のコミットについて話す必要がある場合、「o」の文字は別の文字または数字に置き換えられる場合があります。

履歴の理解:ブランチとは?

厳密には、「ブランチ」という言葉を開発ラインの意味で使い、「ブランチヘッド」(または単に「ヘッド」)をブランチの最新コミットへの参照の意味で使います。上記の例では、「A」という名前のブランチヘッドは特定のコミットへのポインタですが、その時点に至る3つのコミットのライン全体を「ブランチA」の一部であると呼びます。

しかし、混乱が生じない限り、「ブランチ」という用語をブランチとブランチヘッドの両方によく使用します。

ブランチの操作

ブランチの作成、削除、変更は素早く簡単です。コマンドの概要は以下のとおりです。

git branch

すべてのブランチをリスト表示します。

git branch <branch>

現在のブランチと同じ履歴の時点を参照する、<branch>という名前の新しいブランチを作成します。

git branch <branch> <start-point>

<branch>という名前の新しいブランチを作成し、ブランチ名やタグ名を含む、好きな方法で指定できる<start-point>を参照します。

git branch -d <branch>

ブランチ<branch>を削除します。ブランチがその上流ブランチに完全にマージされていない、または現在のブランチに含まれていない場合、このコマンドは警告とともに失敗します。

git branch -D <branch>

マージ状態にかかわらず、ブランチ<branch>を削除します。

git switch <branch>

現在のブランチを<branch>に変更し、ワーキングディレクトリを<branch>が参照するバージョンを反映するように更新します。

git switch -c <new> <start-point>

<start-point>を参照する新しいブランチ<new>を作成し、それをチェックアウトします。

特殊記号「HEAD」は、常に現在のブランチを参照するために使用できます。実際、Gitは.gitディレクトリ内のHEADというファイルを使用して、どのブランチが現在であるかを記憶しています。

$ cat .git/HEAD
ref: refs/heads/master

新しいブランチを作成せずに古いバージョンを検査する

git switchコマンドは通常ブランチヘッドを期待しますが、--detachと一緒に呼び出された場合は任意のコミットも受け入れます。たとえば、タグが参照するコミットをチェックアウトできます。

$ git switch --detach v2.6.17
Note: checking out 'v2.6.17'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another switch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command again. Example:

  git switch -c new_branch_name

HEAD is now at 427abfa Linux v2.6.17

このとき、HEADはブランチではなくコミットのSHA-1を参照し、git branchは現在ブランチ上にいないことを示します。

$ cat .git/HEAD
427abfa28afedffadfca9dd8b067eb6d36bac53f
$ git branch
* (detached from v2.6.17)
  master

この場合、HEADは「デタッチされている」と言います。

これは、新しいブランチに名前を付ける必要なく、特定のバージョンをチェックアウトする簡単な方法です。後でこのバージョンの新しいブランチ(またはタグ)を作成することもできます。

リモートリポジトリからのブランチの検査

クローン時に作成された「master」ブランチは、クローン元のリポジトリのHEADのコピーです。ただし、そのリポジトリには他のブランチがあった可能性もあり、ローカルリポジトリはそれらのリモートブランチのそれぞれを追跡するブランチ、つまりリモート追跡ブランチを保持しており、-rオプションを付けてgit-branch[1]を使用することで表示できます。

$ git branch -r
  origin/HEAD
  origin/html
  origin/maint
  origin/man
  origin/master
  origin/next
  origin/seen
  origin/todo

この例では、「origin」はリモートリポジトリ、または略して「リモート」と呼ばれます。このリポジトリのブランチは、私たちの観点からは「リモートブランチ」と呼ばれます。上記のリストされたリモート追跡ブランチは、クローン時のリモートブランチに基づいて作成され、git fetch(したがってgit pull)とgit pushによって更新されます。詳細については、git fetchによるリポジトリの更新を参照してください。

タグの場合と同様に、これらのリモート追跡ブランチのいずれかを独自のブランチで構築したい場合があります。

$ git switch -c my-todo-copy origin/todo

origin/todoを直接チェックアウトして、それを調べたり、一度限りのパッチを作成したりすることもできます。詳しくはデタッチドヘッドを参照してください。

「origin」という名前は、Gitがデフォルトでクローン元のリポジトリを参照するために使用する名前であることに注意してください。

ブランチ、タグ、その他の参照の名前付け

ブランチ、リモート追跡ブランチ、タグはすべてコミットへの参照です。すべての参照はrefsで始まるスラッシュ区切りのパス名で命名されています。これまでに使用してきた名前は実際には短縮形です。

  • ブランチtestrefs/heads/testの省略形です。

  • タグv2.6.18refs/tags/v2.6.18の省略形です。

  • origin/masterrefs/remotes/origin/masterの省略形です。

フルネームは、たとえば、同じ名前のタグとブランチが共存する場合など、時々役立ちます。

(新しく作成された参照は、実際には.git/refsディレクトリに、その名前で指定されたパスの下に保存されます。ただし、効率上の理由から、それらは単一のファイルにまとめてパックされることもあります。詳細については、git-pack-refs[1]を参照してください)。

もう一つの便利なショートカットとして、リポジトリの「HEAD」は、そのリポジトリの名前だけで参照できます。したがって、たとえば、「origin」は通常、リポジトリ「origin」のHEADブランチのショートカットです。

Gitが参照をチェックするパスの完全なリストと、同じ短縮名を持つ複数の参照がある場合にどちらを選択するかを決定するために使用する順序については、gitrevisions[7]の「SPECIFYING REVISIONS」セクションを参照してください。

git fetchによるリポジトリの更新

リポジトリをクローンして独自の変更をいくつかコミットした後、元のリポジトリの更新を確認したい場合があります。

引数なしのgit-fetchコマンドは、すべてのリモート追跡ブランチを元のリポジトリで見つかった最新バージョンに更新します。独自のブランチ、クローン時に作成された「master」ブランチさえも変更しません。

他のリポジトリからブランチをフェッチする

クローン元のリポジトリ以外のリポジトリのブランチを、git-remote[1]を使用して追跡することもできます。

$ git remote add staging git://git.kernel.org/.../gregkh/staging.git
$ git fetch staging
...
From git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging
 * [new branch]      master     -> staging/master
 * [new branch]      staging-linus -> staging/staging-linus
 * [new branch]      staging-next -> staging/staging-next

新しいリモート追跡ブランチは、git remote addに指定した短縮名(この場合はstaging)の下に保存されます。

$ git branch -r
  origin/HEAD -> origin/master
  origin/master
  staging/master
  staging/staging-linus
  staging/staging-next

後でgit fetch <remote>を実行すると、指定した<remote>のリモート追跡ブランチが更新されます。

.git/configファイルを確認すると、Gitが新しいスタンザを追加していることがわかります。

$ cat .git/config
...
[remote "staging"]
	url = git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git
	fetch = +refs/heads/*:refs/remotes/staging/*
...

これにより、Gitはリモートのブランチを追跡します。これらの設定オプションは、テキストエディタで.git/configを編集することで変更または削除できます。(詳細については、git-config[1]の「CONFIGURATION FILE」セクションを参照してください。)

Git履歴の探索

Gitは、ファイルの集合の履歴を保存するためのツールとして考えるのが最適です。ファイル階層のコンテンツの圧縮されたスナップショットを、これらのスナップショット間の関係を示す「コミット」とともに保存することでこれを行います。

Gitは、プロジェクトの履歴を探索するための非常に柔軟で高速なツールを提供します。

まず、プロジェクトにバグを導入したコミットを見つけるのに役立つ、1つの特殊なツールから始めます。

二分法を使ってリグレッションを見つける方法

プロジェクトのバージョン2.6.18は動作したが、「master」のバージョンはクラッシュすると仮定します。このようなリグレッションの原因を見つける最良の方法は、プロジェクトの履歴をブルートフォース検索して、問題を引き起こした特定のコミットを見つけることです。git-bisect[1]コマンドは、これを行うのに役立ちます。

$ git bisect start
$ git bisect good v2.6.18
$ git bisect bad master
Bisecting: 3537 revisions left to test after this
[65934a9a028b88e83e2b0f8b36618fe503349f8e] BLOCK: Make USB storage depend on SCSI rather than selecting it [try #6]

この時点でgit branchを実行すると、Gitが一時的に「(no branch)」に移動していることがわかります。HEADはどのブランチからも分離され、「master」からは到達可能だがv2.6.18からは到達可能ではないコミット(コミットID 65934)を直接指しています。これをコンパイルしてテストし、クラッシュするかどうかを確認します。クラッシュすると仮定します。その場合、

$ git bisect bad
Bisecting: 1769 revisions left to test after this
[7eff82c8b1511017ae605f0c99ac275a7e21b867] i2c-core: Drop useless bitmaskings

は以前のバージョンをチェックアウトします。このようにして、各段階でGitが与えるバージョンが良いか悪いかをGitに伝え続け、テストに残されたリビジョン数が毎回約半分に削減されることに注目してください。

約13回のテスト後(この場合)、問題のあるコミットのコミットIDが出力されます。その後、git-show[1]でコミットを検査し、誰が作成したかを見つけて、コミットIDを添えてバグレポートを送信できます。最後に、

$ git bisect reset

を実行して、以前にいたブランチに戻ります。

git bisectが各時点でチェックアウトするバージョンは単なる提案であり、良い考えだと思う場合は別のバージョンを試すことができることに注意してください。たとえば、関連性のないものを壊したコミットにたどり着くことがあります。その場合は、

$ git bisect visualize

を実行するとgitkが起動し、「bisect」というマーカーで選択したコミットにラベルが付けられます。近くの安全そうなコミットを選択し、そのコミットIDをメモして、次のコマンドでチェックアウトします。

$ git reset --hard fb47ddb2db

その後テストし、適切にbisect goodまたはbisect badを実行して続行します。

git bisect visualizeの後にgit reset --hard fb47ddb2dbを実行する代わりに、現在のコミットをスキップしたいことをGitに伝えるだけかもしれません。

$ git bisect skip

ただし、この場合、Gitは、スキップされた最初のコミットとそれ以降の悪いコミットとの間で、最初の悪いコミットを最終的に特定できない可能性があります。

良いコミットと悪いコミットを区別できるテストスクリプトがある場合は、二分探索プロセスを自動化する方法もあります。これとその他のgit bisect機能の詳細については、git-bisect[1]を参照してください。

コミットの命名

すでにコミットを命名するいくつかの方法を見てきました。

  • 40桁の16進数オブジェクト名

  • ブランチ名:指定されたブランチのヘッドにあるコミットを参照します。

  • タグ名:指定されたタグが指すコミットを参照します(ブランチとタグが参照の特殊なケースであることを確認しました)。

  • HEAD:現在のブランチのHEADを参照します。

さらに多くの方法があります。リビジョンを命名するすべての方法の完全なリストについては、gitrevisions[7] manページの「SPECIFYING REVISIONS」セクションを参照してください。いくつかの例を挙げます。

$ git show fb47ddb2 # the first few characters of the object name
		    # are usually enough to specify it uniquely
$ git show HEAD^    # the parent of the HEAD commit
$ git show HEAD^^   # the grandparent
$ git show HEAD~4   # the great-great-grandparent

マージコミットには複数の親がある場合があることを思い出してください。デフォルトでは、^~はコミットにリストされている最初の親に従いますが、選択することもできます。

$ git show HEAD^1   # show the first parent of HEAD
$ git show HEAD^2   # show the second parent of HEAD

HEADに加えて、コミットには他にもいくつかの特殊な名前があります。

マージ(後述)や、現在チェックアウトされているコミットを変更するgit resetなどの操作は、通常、現在の操作前のHEADの値をORIG_HEADに設定します。

git fetch操作は、常に最後にフェッチされたブランチのヘッドをFETCH_HEADに保存します。たとえば、操作のターゲットとしてローカルブランチを指定せずにgit fetchを実行した場合、

$ git fetch git://example.com/proj.git theirbranch

フェッチされたコミットはFETCH_HEADから引き続き利用可能です。

マージについて議論する際に、MERGE_HEADという特別な名前も登場します。これは、現在のブランチにマージする別のブランチを指します。

git-rev-parse[1]コマンドは、コミットの特定の名前をそのコミットのオブジェクト名に変換するのに時々役立つ低レベルのコマンドです。

$ git rev-parse origin
e05db0fd4f31dde7005f075a84f96b360d05984b

タグの作成

特定のコミットを参照するタグを作成することもできます。実行後、

$ git tag stable-1 1b2e1d63ff

stable-1を使用してコミット1b2e1d63ffを参照できます。

これにより、「軽量」タグが作成されます。タグにコメントを含めたり、暗号署名したりしたい場合は、代わりにタグオブジェクトを作成する必要があります。詳細については、git-tag[1] manページを参照してください。

リビジョンのブラウズ

git-log[1]コマンドはコミットのリストを表示できます。それ自体では、親コミットから到達可能なすべてのコミットを表示しますが、より具体的な要求を行うこともできます。

$ git log v2.5..	# commits since (not reachable from) v2.5
$ git log test..master	# commits reachable from master but not test
$ git log master..test	# ...reachable from test but not master
$ git log master...test	# ...reachable from either test or master,
			#    but not both
$ git log --since="2 weeks ago" # commits from the last 2 weeks
$ git log Makefile      # commits which modify Makefile
$ git log fs/		# ... which modify any file under fs/
$ git log -S'foo()'	# commits which add or remove any file data
			# matching the string 'foo()'

そしてもちろん、これらをすべて組み合わせることができます。以下は、v2.5以降でMakefileまたはfs以下のファイルに触れるコミットを見つけます。

$ git log v2.5.. Makefile fs/

また、git logにパッチを表示させることもできます。

$ git log -p

その他の表示オプションについては、git-log[1] manページの--prettyオプションを参照してください。

git logは最新のコミットから親をさかのぼって処理しますが、Git履歴には複数の独立した開発ラインが含まれる可能性があるため、コミットがリストされる特定の順序はやや任意であることに注意してください。

差分の生成

git-diff[1]を使用して、任意の2つのバージョン間の差分を生成できます。

$ git diff master..test

これにより、2つのブランチの先端間の差分が生成されます。共通の祖先からテストまでの差分を見つけたい場合は、2つの点ではなく3つの点を使用できます。

$ git diff master...test

代わりに一連のパッチが必要な場合もあります。この場合は、git-format-patch[1]を使用できます。

$ git format-patch master..test

は、testから到達可能だがmasterからは到達可能ではない各コミットのパッチを含むファイルを現在のディレクトリに生成します。

古いファイルバージョンの表示

常に、まず正しいリビジョンをチェックアウトすることで、ファイルの古いバージョンを表示できます。しかし、何もチェックアウトせずに単一ファイルの古いバージョンを表示できる方が便利な場合もあります。このコマンドはそれを実行します。

$ git show v2.5:fs/locks.c

コロンの前にはコミットを指定する任意のもの、後にはGitによって追跡されているファイルへの任意のパスを指定できます。

ブランチ上のコミット数を数える

mybranchoriginから分岐して以来、いくつのコミットを行ったかを知りたいとします。

$ git log --pretty=oneline origin..mybranch | wc -l

あるいは、この種のことは、与えられたすべてのコミットのSHA-1をリストするだけの低レベルコマンドgit-rev-list[1]でよく行われているのを目にすることがあります。

$ git rev-list origin..mybranch | wc -l

2つのブランチが同じ履歴を指しているかを確認する

2つのブランチが履歴の同じ時点を指しているかを確認したいとします。

$ git diff origin..master

は、プロジェクトの内容が2つのブランチで同じかどうかを教えてくれます。しかし、理論的には、同じプロジェクト内容が2つの異なる歴史的ルートによって到達した可能性があります。オブジェクト名を比較することができます。

$ git rev-list origin
e05db0fd4f31dde7005f075a84f96b360d05984b
$ git rev-list master
e05db0fd4f31dde7005f075a84f96b360d05984b

あるいは、...演算子が、一方の参照または他方の参照から到達可能だが両方からは到達可能ではないすべてのコミットを選択することを思い出すことができます。したがって、

$ git log origin...master

は、2つのブランチが等しい場合、コミットを返しません。

指定された修正を含む最初のタグ付きバージョンを見つける

コミットe05db0fdが特定の問題を修正したことを知っているとします。その修正を含む最も古いタグ付きリリースを見つけたいと考えています。

もちろん、答えは複数あるかもしれません。コミットe05db0fdの後に履歴が分岐した場合、複数の「最も古い」タグ付きリリースが存在する可能性があります。

e05db0fd以降のコミットを視覚的に検査することもできます。

$ gitk e05db0fd..

または、git-name-rev[1]を使用できます。これは、コミットの子孫のいずれかを指すタグに基づいてコミットに名前を付けます。

$ git name-rev --tags e05db0fd
e05db0fd tags/v1.5.0-rc1^0~23

git-describe[1]コマンドは反対の動作を行い、指定されたコミットが基づいているタグを使用してリビジョンに名前を付けます。

$ git describe e05db0fd
v1.5.0-rc0-260-ge05db0f

しかし、それは与えられたコミットの後に来るタグを推測するのに役立つ場合があります。

特定のタグ付けされたバージョンが特定のコミットを含んでいるかどうかを確認したいだけの場合は、git-merge-base[1]を使用できます。

$ git merge-base e05db0fd v1.5.0-rc1
e05db0fd4f31dde7005f075a84f96b360d05984b

merge-baseコマンドは、指定されたコミットの共通の祖先を見つけ、一方がもう一方の子孫である場合は常にどちらか一方を返します。したがって、上記の出力は、e05db0fdが実際にv1.5.0-rc1の祖先であることを示しています。

あるいは、次の点に注意してください。

$ git log v1.5.0-rc1..e05db0fd

v1.5.0-rc1から到達できないコミットのみを出力するため、v1.5.0-rc1にe05db0fdが含まれている場合にのみ空の出力を生成します。

もう一つの選択肢として、git-show-branch[1]コマンドは、引数から到達可能なコミットを左側に表示し、そのコミットがどの引数から到達可能かを示します。したがって、次のようなコマンドを実行すると、

$ git show-branch e05db0fd v1.5.0-rc0 v1.5.0-rc1 v1.5.0-rc2
! [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format if
available
 ! [v1.5.0-rc0] GIT v1.5.0 preview
  ! [v1.5.0-rc1] GIT v1.5.0-rc1
   ! [v1.5.0-rc2] GIT v1.5.0-rc2
...

次のような行は、

+ ++ [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format if
available

e05db0fdはそれ自体から、v1.5.0-rc1から、そしてv1.5.0-rc2から到達可能であり、v1.5.0-rc0からは到達可能ではないことを示しています。

特定のブランチに固有のコミットを表示する

masterという名前のブランチヘッドから到達可能だが、リポジトリ内の他のどのヘッドからも到達可能ではないすべてのコミットを表示したいとします。

このリポジトリのすべてのヘッドをgit-show-ref[1]でリストできます。

$ git show-ref --heads
bf62196b5e363d73353a9dcf094c59595f3153b7 refs/heads/core-tutorial
db768d5504c1bb46f63ee9d6e1772bd047e05bf9 refs/heads/maint
a07157ac624b2524a059a3414e99f6f44bebc1e7 refs/heads/master
24dbc180ea14dc1aebe09f14c8ecf32010690627 refs/heads/tutorial-2
1e87486ae06626c2f31eaa63d26fc0fd646c8af2 refs/heads/tutorial-fixes

標準ユーティリティのcutとgrepを使って、ブランチヘッド名だけを取得し、masterを削除できます。

$ git show-ref --heads | cut -d' ' -f2 | grep -v '^refs/heads/master'
refs/heads/core-tutorial
refs/heads/maint
refs/heads/tutorial-2
refs/heads/tutorial-fixes

そして、masterから到達可能だが、これらの他のヘッドからは到達可能ではないすべてのコミットを表示するように要求できます。

$ gitk master --not $( git show-ref --heads | cut -d' ' -f2 |
				grep -v '^refs/heads/master' )

もちろん、無限のバリエーションが可能です。例えば、リポジトリ内のいずれかのヘッドから到達可能だが、どのタグからも到達可能ではないすべてのコミットを見るには、

$ gitk $( git show-ref --heads ) --not  $( git show-ref --tags )

--notなどのコミット選択構文の説明については、gitrevisions[7]を参照してください)。

ソフトウェアリリース用の変更履歴とtarボールの作成

git-archive[1]コマンドは、プロジェクトの任意のバージョンからtarまたはzipアーカイブを作成できます。たとえば、

$ git archive -o latest.tar.gz --prefix=project/ HEAD

はHEADを使用して、各ファイル名の前にproject/が付くgzip圧縮されたtarアーカイブを作成します。出力ファイル形式は可能であれば出力ファイル拡張子から推測されます。詳細についてはgit-archive[1]を参照してください。

Gitのバージョン1.7.7より前のバージョンはtar.gz形式を知らないため、明示的にgzipを使用する必要があります。

$ git archive --format=tar --prefix=project/ HEAD | gzip >latest.tar.gz

ソフトウェアプロジェクトの新しいバージョンをリリースする場合、同時に変更履歴を作成してリリースアナウンスに含めたい場合があります。

例えば、Linus Torvaldsは、新しいカーネルリリースをタグ付けし、その後、

$ release-script 2.6.12 2.6.13-rc6 2.6.13-rc7

ここで、release-scriptは次のようなシェルスクリプトです。

#!/bin/sh
stable="$1"
last="$2"
new="$3"
echo "# git tag v$new"
echo "git archive --prefix=linux-$new/ v$new | gzip -9 > ../linux-$new.tar.gz"
echo "git diff v$stable v$new | gzip -9 > ../patch-$new.gz"
echo "git log --no-merges v$new ^v$last > ../ChangeLog-$new"
echo "git shortlog --no-merges v$new ^v$last > ../ShortLog"
echo "git diff --stat --summary -M v$last v$new > ../diffstat-$new"

そして、彼は出力コマンドが問題ないことを確認した後、それを切り貼りするだけです。

特定のコンテンツを持つファイルを参照するコミットを見つける

誰かがあなたにファイルのコピーを渡し、そのコミットの前後にその特定のコンテンツを含むようにファイルを変更したコミットを尋ねてきました。次のようにして見つけることができます。

$  git log --raw --abbrev=40 --pretty=oneline |
	grep -B 1 `git hash-object filename`

なぜこれが機能するかを理解することは、(上級)学生への演習として残されています。git-log[1]git-diff-tree[1]、およびgit-hash-object[1]のmanページが役立つでしょう。

Gitでの開発

Gitに自分の名前を伝える

コミットを作成する前に、Gitに自己紹介をする必要があります。最も簡単な方法は、git-config[1]を使用することです。

$ git config --global user.name 'Your Name Comes Here'
$ git config --global user.email 'you@yourdomain.example.com'

これにより、ホームディレクトリの.gitconfigというファイルに以下が追加されます。

[user]
	name = Your Name Comes Here
	email = you@yourdomain.example.com

設定ファイルの詳細については、git-config[1]の「CONFIGURATION FILE」セクションを参照してください。ファイルはプレーンテキストなので、お気に入りのエディタで編集することもできます。

新しいリポジトリの作成

新しいリポジトリを一から作成するのは非常に簡単です。

$ mkdir project
$ cd project
$ git init

初期コンテンツがある場合(例えば、tarball)、

$ tar xzvf project.tar.gz
$ cd project
$ git init
$ git add . # include everything below ./ in the first commit:
$ git commit

コミットの作成方法

新しいコミットを作成するには、3つのステップが必要です。

  1. お気に入りのエディタを使用して、ワーキングディレクトリにいくつかの変更を加えます。

  2. Gitに変更を伝えます。

  3. ステップ2でGitに伝えたコンテンツを使用してコミットを作成します。

実際には、ステップ1と2を好きなだけ交互に繰り返すことができます。ステップ3でコミットしたい内容を追跡するために、Gitは「インデックス」と呼ばれる特別なステージングエリアにツリーのコンテンツのスナップショットを保持しています。

最初は、インデックスの内容はHEADの内容と同一になります。したがって、HEADとインデックスの差分を表示するgit diff --cachedコマンドは、その時点では何も出力しないはずです。

インデックスの変更は簡単です。

新規または変更されたファイルの内容でインデックスを更新するには、

$ git add path/to/file

インデックスおよびワーキングツリーからファイルを削除するには、

$ git rm path/to/file

各ステップの後、次のことを確認できます。

$ git diff --cached

は常にHEADとインデックスファイルの差分を表示します。これは、今コミットを作成した場合にコミットされる内容です。そして、

$ git diff

はワーキングツリーとインデックスファイルの差分を表示します。

git addは常にファイルの現在の内容のみをインデックスに追加することに注意してください。同じファイルへのさらなる変更は、再度git addを実行しない限り無視されます。

準備ができたら、単に

$ git commit

を実行すると、Gitはコミットメッセージの入力を促し、新しいコミットを作成します。次のコマンドで期待どおりであることを確認してください。

$ git show

特別なショートカットとして、

$ git commit -a

は、変更または削除されたすべてのファイルをインデックスに更新し、コミットを作成します。これらすべてを一度に行います。

コミットしようとしているものを追跡するのに役立つコマンドがいくつかあります。

$ git diff --cached # difference between HEAD and the index; what
		    # would be committed if you ran "commit" now.
$ git diff	    # difference between the index file and your
		    # working directory; changes that would not
		    # be included if you ran "commit" now.
$ git diff HEAD	    # difference between HEAD and working tree; what
		    # would be committed if you ran "commit -a" now.
$ git status	    # a brief per-file summary of the above.

git-gui[1]を使用して、コミットを作成したり、インデックスおよびワーキングツリーファイルの変更を表示したり、個々の差分ハンクをインデックスに含めるために選択したり(差分ハンクを右クリックして「Stage Hunk For Commit」を選択する)こともできます。

良いコミットメッセージの作成

必須ではありませんが、コミットメッセージは、変更の概要をまとめた短い(50文字以内)1行で始め、その後に空行を挟んで、より詳細な説明を続けるのが良い習慣です。コミットメッセージの最初の空行までのテキストはコミットのタイトルとして扱われ、そのタイトルはGit全体で使用されます。たとえば、git-format-patch[1]はコミットを電子メールに変換し、件名行にタイトルを、残りのコミットを本文に使用します。

ファイルの無視

プロジェクトでは、Gitで追跡したくないファイルが生成されることがよくあります。これには通常、ビルドプロセスによって生成されたファイルや、エディタによって作成された一時的なバックアップファイルが含まれます。もちろん、Gitでファイルを追跡「しない」のは、それらに対してgit addを呼び出さないことだけの問題です。しかし、これらの追跡されていないファイルが散らばっているとすぐに煩わしくなります。たとえば、git add .が実質的に役に立たなくなったり、git statusの出力に表示され続けたりします。

ワーキングディレクトリの最上位に.gitignoreというファイルを作成し、次のような内容にすることで、Gitに特定のファイルを無視するように指示できます。

# Lines starting with '#' are considered comments.
# Ignore any file named foo.txt.
foo.txt
# Ignore (generated) html files,
*.html
# except foo.html which is maintained by hand.
!foo.html
# Ignore objects and archives.
*.[oa]

構文の詳細については、gitignore[5]を参照してください。.gitignoreファイルは、ワーキングツリー内の他のディレクトリに配置することもでき、それらのディレクトリとそのサブディレクトリに適用されます。.gitignoreファイルは、他のファイルと同様にリポジトリに追加できます(通常どおりgit add .gitignoregit commitを実行するだけです)。これは、除外パターン(ビルド出力ファイルに一致するパターンなど)がリポジトリをクローンする他のユーザーにも意味がある場合に便利です。

除外パターンが特定の(プロジェクト全体のすべてのリポジトリではなく)リポジトリにのみ影響するようにしたい場合は、代わりにリポジトリ内の.git/info/excludeというファイル、またはcore.excludesFile構成変数で指定された任意のファイルに入れることができます。一部のGitコマンドは、コマンドラインで除外パターンを直接受け取ることもできます。詳細については、gitignore[5]を参照してください。

マージ方法

git-merge[1]を使用して、2つの異なる開発ブランチを再結合できます。

$ git merge branchname

は、ブランチbranchnameの開発を現在のブランチにマージします。

マージは、branchnameで行われた変更と、履歴が分岐してからの現在のブランチの最新コミットまでの変更を組み合わせることによって行われます。この結合がクリーンに行われた場合、作業ツリーはマージの結果によって上書きされます。この結合が衝突を引き起こした場合、半マージされた結果によって上書きされます。したがって、マージによって影響を受けるファイルと同じファイルを変更した未コミットの変更がある場合、Gitは続行を拒否します。ほとんどの場合、マージする前に変更をコミットしたいと思うでしょう。コミットしない場合、git-stash[1]はマージ中にこれらの変更を一時的に取り除き、後で再適用できます。

変更が十分に独立している場合、Gitは自動的にマージを完了し、結果をコミットします(または、早送りの場合、既存のコミットを再利用します。以下を参照)。一方、競合がある場合、たとえば、リモートブランチとローカルブランチで同じファイルが2つの異なる方法で変更された場合、警告が表示されます。出力は次のようになります。

$ git merge next
 100% (4/4) done
Auto-merged file.txt
CONFLICT (content): Merge conflict in file.txt
Automatic merge failed; fix conflicts and then commit the result.

問題のあるファイルには競合マーカーが残され、競合を手動で解決した後、内容でインデックスを更新し、新しいファイルを作成するときと同じようにGitコミットを実行できます。

gitkを使って結果のコミットを調べると、2つの親を持つことがわかります。1つは現在のブランチの先端を指し、もう1つはもう一方のブランチの先端を指しています。

マージの解決

マージが自動的に解決されない場合、Gitはインデックスとワーキングツリーを特殊な状態のままにし、マージを解決するのに必要なすべての情報を提供します。

競合のあるファイルはインデックスで特別にマークされるため、問題を解決してインデックスを更新するまで、git-commit[1]は失敗します。

$ git commit
file.txt: needs merge

また、git-status[1]はこれらのファイルを「unmerged」としてリストし、競合のあるファイルには次のような競合マーカーが追加されます。

<<<<<<< HEAD:file.txt
Hello world
=======
Goodbye
>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt

競合を解決するためにファイルを編集し、その後、次のコマンドを実行するだけです。

$ git add file.txt
$ git commit

コミットメッセージは、マージに関する情報で既に記入されていることに注意してください。通常、このデフォルトメッセージは変更せずに使用できますが、必要に応じて独自の追加コメントを追加することもできます。

上記は、単純なマージを解決するために知っておくべきすべてのことです。しかし、Gitは競合の解決に役立つさらに多くの情報を提供します。

マージ中の競合解決のヘルプ

Gitが自動的にマージできたすべての変更は、すでにインデックスファイルに追加されているため、git-diff[1]は競合のみを表示します。それは異常な構文を使用します。

$ git diff
diff --cc file.txt
index 802992c,2b60207..0000000
--- a/file.txt
+++ b/file.txt
@@@ -1,1 -1,1 +1,5 @@@
++<<<<<<< HEAD:file.txt
 +Hello world
++=======
+ Goodbye
++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt

この競合を解決した後にコミットされるコミットには、通常の1つではなく2つの親があることを思い出してください。1つの親はHEAD(現在のブランチの先端)、もう1つの親はMERGE_HEADに一時的に格納されている別のブランチの先端です。

マージ中、インデックスは各ファイルの3つのバージョンを保持します。これら3つの「ファイルステージ」は、それぞれ異なるバージョンのファイルを表します。

$ git show :1:file.txt	# the file in a common ancestor of both branches
$ git show :2:file.txt	# the version from HEAD.
$ git show :3:file.txt	# the version from MERGE_HEAD.

git-diff[1]に競合を表示するように要求すると、作業ツリーの競合するマージ結果とステージ2とステージ3の間で3方向差分を実行し、両側からのコンテンツのみを含むハンクを表示します(つまり、ハンクのマージ結果がステージ2のみから来る場合、その部分は競合しておらず、表示されません。ステージ3も同様です)。

上記の差分は、file.txtのワーキングツリーバージョンとステージ2およびステージ3バージョンとの差分を示しています。したがって、各行の前に単一の+または-を付ける代わりに、2つの列を使用します。最初の列は最初の親とワーキングディレクトリコピー間の差分に、2番目の列は2番目の親とワーキングディレクトリコピー間の差分に使用されます。(形式の詳細については、git-diff-files[1]の「COMBINED DIFF FORMAT」セクションを参照してください。)

明らかな方法で競合を解決した後(ただしインデックスを更新する前)、差分は次のようになります。

$ git diff
diff --cc file.txt
index 802992c,2b60207..0000000
--- a/file.txt
+++ b/file.txt
@@@ -1,1 -1,1 +1,1 @@@
- Hello world
 -Goodbye
++Goodbye world

これは、解決されたバージョンが最初の親から「Hello world」を削除し、2番目の親から「Goodbye」を削除し、両方から以前は存在しなかった「Goodbye world」を追加したことを示しています。

いくつかの特別なdiffオプションを使用すると、これらのステージのいずれかとワーキングディレクトリをdiffできます。

$ git diff -1 file.txt		# diff against stage 1
$ git diff --base file.txt	# same as the above
$ git diff -2 file.txt		# diff against stage 2
$ git diff --ours file.txt	# same as the above
$ git diff -3 file.txt		# diff against stage 3
$ git diff --theirs file.txt	# same as the above.

ortマージ戦略(デフォルト)を使用する場合、マージの結果で作業ツリーを更新する前に、Gitは書き込もうとしているツリーの状態を反映するAUTO_MERGEという名前の参照を書き込みます。自動的にマージできなかったテキスト競合のある競合パスは、作業ツリーと同様に競合マーカー付きでこのツリーに書き込まれます。したがって、AUTO_MERGEはgit-diff[1]と共に使用して、競合を解決するためにこれまでに加えた変更を表示できます。上記の例と同じ例を使用して、競合を解決すると、次のようになります。

$ git diff AUTO_MERGE
diff --git a/file.txt b/file.txt
index cd10406..8bf5ae7 100644
--- a/file.txt
+++ b/file.txt
@@ -1,5 +1 @@
-<<<<<<< HEAD:file.txt
-Hello world
-=======
-Goodbye
->>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
+Goodbye world

差分が示すように、競合マーカーとコンテンツ行の両方のバージョンを削除し、代わりに「Goodbye world」を書き込みました。

git-log[1]gitk[1]コマンドも、マージに関する特別なヘルプを提供します。

$ git log --merge
$ gitk --merge

これらは、HEADまたはMERGE_HEADにのみ存在し、かつマージされていないファイルに触れるすべてのコミットを表示します。

また、Emacsやkdiff3などの外部ツールを使って未マージファイルをマージできるgit-mergetool[1]も使用できます。

ファイルを解決してインデックスを更新するたびに、

$ git add file.txt

そのファイルの異なるステージは「折りたたまれ」、その後git diffは(デフォルトでは)そのファイルの差分を表示しなくなります。

マージの取り消し

行き詰まってしまい、この混乱をすべて諦めて破棄することにした場合は、いつでも次のコマンドでマージ前の状態に戻ることができます。

$ git merge --abort

または、破棄したいマージをすでにコミットしている場合、

$ git reset --hard ORIG_HEAD

ただし、この最後のコマンドは場合によっては危険です。すでに公開されているコミットが、さらに別のブランチにマージされている可能性がある場合、そのコミットを破棄しないでください。そうすると、それ以降のマージが混乱する可能性があります。

早送りマージ

上記で述べられていない特別なケースが1つあり、それは異なって扱われます。通常、マージは2つの親を持つマージコミットを生成します。各親はマージされた2つの開発ラインのいずれかを指します。

しかし、現在のブランチが他のブランチの祖先である場合、つまり、現在のブランチに存在するすべてのコミットがすでに他のブランチに含まれている場合、Gitは単に「早送り」を実行します。現在のブランチのヘッドは、新しいコミットが作成されることなく、マージされたブランチのヘッドを指すように前方に移動します。

間違いの修正

ワーキングツリーを台無しにしてしまったが、まだ間違いをコミットしていない場合は、次のコマンドでワーキングツリー全体を最後のコミットされた状態に戻すことができます。

$ git restore --staged --worktree :/

後でコミットしなければよかったと思うコミットをしてしまった場合、問題を修正するには根本的に異なる2つの方法があります。

  1. 古いコミットによって行われたことを元に戻す新しいコミットを作成できます。この方法は、間違いがすでに公開されている場合に適切な方法です。

  2. 古いコミットに戻って修正できます。履歴をすでに公開している場合は、これを決して行うべきではありません。Gitは通常、プロジェクトの「履歴」が変更されることを想定しておらず、履歴が変更されたブランチからの繰り返しのマージを正しく実行できません。

新しいコミットで間違いを修正する

以前の変更を元に戻す新しいコミットを作成するのは非常に簡単です。不良なコミットへの参照をgit-revert[1]コマンドに渡すだけです。たとえば、最新のコミットを元に戻すには、

$ git revert HEAD

これにより、HEADの変更を元に戻す新しいコミットが作成されます。新しいコミットのコミットメッセージを編集する機会が与えられます。

以前の変更、たとえば最後から2番目の変更を元に戻すこともできます。

$ git revert HEAD^

この場合、Gitは以前の変更を元に戻そうとしますが、それ以降に行われた変更はそのまま残します。最近の変更が元に戻す変更と重複する場合、マージの解決の場合と同様に、手動で競合を修正するように求められます。

履歴を書き換えることによる間違いの修正

問題のあるコミットが最新のコミットであり、まだそのコミットを公開していない場合、git resetを使用してそれを破棄できます。

あるいは、作業ディレクトリを編集し、インデックスを更新して間違いを修正し、まるで新しいコミットを作成するかのように、次のコマンドを実行します。

$ git commit --amend

これにより、古いコミットはあなたの変更を取り込んだ新しいコミットに置き換えられ、まず古いコミットメッセージを編集する機会が与えられます。

繰り返しになりますが、すでに他のブランチにマージされている可能性があるコミットに対してこれを行うべきではありません。その場合は代わりにgit-revert[1]を使用してください。

履歴のさらに古いコミットを置き換えることも可能ですが、これは別の章で説明する上級トピックです。

ファイルの古いバージョンのチェックアウト

以前の悪い変更を元に戻す過程で、git-restore[1]を使用して特定のファイルの古いバージョンをチェックアウトすると便利だと感じるかもしれません。コマンド

$ git restore --source=HEAD^ path/to/file

は、path/to/fileをコミットHEAD^にあった内容に置き換え、インデックスも一致するように更新します。ブランチは変更しません。

作業ディレクトリを変更せずにファイルの古いバージョンを見たいだけであれば、git-show[1]でそれを行うことができます。

$ git show HEAD^:path/to/file

は、ファイルの指定されたバージョンを表示します。

進行中の作業を一時的に脇に置く

複雑な作業の途中で、無関係だが明白で些細なバグを発見したとします。先に進む前にそれを修正したいとします。git-stash[1]を使用して現在の作業状態を保存し、バグを修正した後(または、必要に応じて別のブランチで修正した後、戻ってきてから)、進行中の変更をスタッシュから取り出します。

$ git stash push -m "work in progress for foo feature"

このコマンドは、変更をstashに保存し、ワーキングツリーとインデックスを現在のブランチの先端と一致するようにリセットします。その後、通常どおり修正を行うことができます。

... edit and test ...
$ git commit -a -m "blorpl: typofix"

その後、git stash popで作業していた内容に戻ることができます。

$ git stash pop

優れたパフォーマンスの確保

大規模なリポジトリでは、Gitは履歴情報がディスクやメモリに過剰なスペースを占有しないように、圧縮に依存しています。一部のGitコマンドはgit-gc[1]を自動的に実行するため、手動で実行することを心配する必要はありません。ただし、大規模なリポジトリを圧縮するには時間がかかる場合があるため、便利なときに自動圧縮が開始されないように、gcを明示的に呼び出すことをお勧めします。

信頼性の確保

リポジトリの破損チェック

git-fsck[1]コマンドは、リポジトリに対していくつかの自己整合性チェックを実行し、問題があれば報告します。これには時間がかかる場合があります。

$ git fsck
dangling commit 7281251ddd2a61e38657c827739c57015671a6b3
dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63
dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5
dangling blob 218761f9d90712d37a9c5e36f406f92202db07eb
dangling commit bf093535a34a4d35731aa2bd90fe6b176302f14f
dangling commit 8e4bec7f2ddaa268bef999853c25755452100f8e
dangling tree d50bb86186bf27b681d25af89d3b5b68382e4085
dangling tree b24c2473f1fd3d91352a624795be026d64c8841f
...

浮遊オブジェクトに関する情報メッセージが表示されます。これらはリポジトリにまだ存在しますが、どのブランチからも参照されなくなったオブジェクトであり、しばらくするとgcで削除される可能性があります(そして削除されます)。これらのメッセージを抑制し、実際のエラーを表示するには、git fsck --no-danglingを実行します。

失われた変更の回復

Reflog

git reset --hardでブランチを修正し、その後、そのブランチが履歴のその時点への唯一の参照であったことに気づいたとします。

幸いなことに、Gitは、各ブランチの以前のすべての値のログ(「reflog」と呼ばれる)も保持しています。したがって、この場合、たとえば次のようにして古い履歴を見つけることができます。

$ git log master@{1}

これは、masterブランチヘッドの以前のバージョンから到達可能なコミットをリストします。この構文は、git logだけでなく、コミットを受け入れる任意のGitコマンドで使用できます。他のいくつかの例を挙げます。

$ git show master@{2}		# See where the branch pointed 2,
$ git show master@{3}		# 3, ... changes ago.
$ gitk master@{yesterday}	# See where it pointed yesterday,
$ gitk master@{"1 week ago"}	# ... or last week
$ git log --walk-reflogs master	# show reflog entries for master

HEADには個別のreflogが保持されているため、

$ git show HEAD@{"1 week ago"}

は、1週間前のHEADが指していたものを示し、現在のブランチが1週間前に指していたものではありません。これにより、チェックアウトした履歴を確認できます。

reflogはデフォルトで30日間保持され、その後はプルーニングされる可能性があります。このプルーニングを制御する方法については、git-reflog[1]およびgit-gc[1]を参照し、詳細についてはgitrevisions[7]の「SPECIFYING REVISIONS」セクションを参照してください。

reflog履歴は通常のGit履歴とは非常に異なることに注意してください。通常の履歴は同じプロジェクトで作業するすべてのリポジトリで共有されますが、reflog履歴は共有されません。ローカルリポジトリのブランチが時間の経過とともにどのように変化したかのみを伝えます。

ぶら下がりオブジェクトの検査

状況によっては、reflogが役に立たない場合があります。たとえば、ブランチを削除した後、それが含む履歴が必要なことに気づいたとします。reflogも削除されます。ただし、まだリポジトリを整理していない場合でも、git fsckが報告するぶら下がりオブジェクトの中に失われたコミットを見つけることができるかもしれません。詳細については、ぶら下がりオブジェクトを参照してください。

$ git fsck
dangling commit 7281251ddd2a61e38657c827739c57015671a6b3
dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63
dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5
...

それらのぶら下がりコミットの1つを、たとえば、次のように検査できます。

$ gitk 7281251ddd --not --all

これは、ぶら下がりコミットによって記述されたコミット履歴を見たいが、既存のすべてのブランチやタグによって記述された履歴ではない、という意味です。これにより、失われたコミットから到達可能な履歴を正確に取得できます。(そして、それが1つのコミットだけではない可能性があることに注意してください。私たちは「ラインの先端」がぶら下がっていると報告するだけですが、そこには深く複雑なコミット履歴全体が削除された可能性があります。)

履歴を元に戻したい場合は、常に新しい参照(たとえば、新しいブランチ)を作成して参照できます。

$ git branch recovered-branch 7281251ddd

その他の種類のぶら下がりオブジェクト(ブロブやツリー)も可能であり、他の状況でもぶら下がりオブジェクトが発生する可能性があります。

他者との開発の共有

git pullによる更新の取得

リポジトリをクローンして独自の変更をいくつかコミットした後、元のリポジトリの更新を確認し、それらを自分の作業にマージしたい場合があります。

git-fetch[1]を使用してリモート追跡ブランチを最新の状態に保つ方法と、2つのブランチをマージする方法はすでに説明しました。したがって、元のリポジトリのマスターブランチからの変更を次のコマンドでマージできます。

$ git fetch
$ git merge origin/master

ただし、git-pull[1]コマンドはこれを1つのステップで行う方法を提供します。

$ git pull origin master

実際、masterをチェックアウトしている場合、このブランチはgit cloneによって、オリジンリポジトリのHEADブランチから変更を取得するように構成されています。したがって、多くの場合、単純に次のコマンドで上記の操作を実行できます。

$ git pull

このコマンドは、リモートブランチからの変更をリモート追跡ブランチorigin/*にフェッチし、デフォルトブランチを現在のブランチにマージします。

より一般的には、リモート追跡ブランチから作成されたブランチは、デフォルトでそのブランチからプルします。branch.<name>.remotebranch.<name>.mergeオプションの説明はgit-config[1]--trackオプションの議論はgit-checkout[1]を参照して、これらのデフォルトを制御する方法を学んでください。

キーボード入力の手間を省くことに加えて、git pullは、プル元のブランチとリポジトリを文書化したデフォルトのコミットメッセージを生成することで役立ちます。

(ただし、早送りの場合、そのようなコミットは作成されず、代わりに、ブランチはアップストリームブランチの最新のコミットを指すように更新されるだけであることに注意してください。)

git pullコマンドは、「リモート」リポジトリとして.を指定することもできます。この場合、現在のリポジトリからブランチをマージするだけです。したがって、次のコマンドは、

$ git pull . branch
$ git merge branch

はほぼ同等です。

プロジェクトへのパッチの提出

変更が少ない場合、それらを提出する最も簡単な方法は、電子メールでパッチとして送信することかもしれません。

まず、git-format-patch[1]を使用します。たとえば、

$ git format-patch origin

は、現在のディレクトリに番号付きのファイルシリーズを生成します。現在のブランチにあるがorigin/HEADにはない各パッチに対して1つです。

git format-patchは、最初の「カバーレター」を含めることができます。format-patchがコミットメッセージの後、パッチ自体の前に配置する3つのダッシュラインの後に、個々のパッチに関するコメントを挿入できます。git notesを使用してカバーレターの素材を追跡する場合、git format-patch --notesはコミットのメモを同様の方法で含めます。

その後、これらをメールクライアントにインポートし、手動で送信できます。ただし、一度に大量に送信する必要がある場合は、git-send-email[1]スクリプトを使用してプロセスを自動化することをお勧めします。パッチ提出の要件を決定するために、まずプロジェクトのメーリングリストを参照してください。

プロジェクトへのパッチのインポート

Gitは、そのような電子メールで送信された一連のパッチをインポートするためのgit-am[1](amは「apply mailbox」の略)というツールも提供しています。パッチを含むすべてのメッセージを、順番に単一のメールボックスファイル(例: patches.mbox)に保存し、次のコマンドを実行するだけです。

$ git am -3 patches.mbox

Gitは各パッチを順番に適用します。競合が見つかった場合は停止し、「マージの解決」で説明されているように競合を修正できます。(-3オプションはGitにマージを実行するように指示します。中止してツリーとインデックスをそのまま残したい場合は、このオプションを省略できます)。

競合解決の結果でインデックスが更新されたら、新しいコミットを作成する代わりに、次のコマンドを実行するだけです。

$ git am --continue

これにより、Gitはコミットを作成し、メールボックスからの残りのパッチの適用を続行します。

最終結果は、元のメールボックスの各パッチごとに1つのコミットのシリーズとなり、作成者とコミットログメッセージはそれぞれ各パッチを含むメッセージから取得されます。

公開Gitリポジトリ

プロジェクトに変更を提出するもう1つの方法は、git-pull[1]を使用して、そのプロジェクトのメンテナーにあなたのリポジトリから変更をプルするように伝えることです。「git pullによる更新の取得」のセクションでは、これを「メイン」リポジトリから更新を取得する方法として説明しましたが、逆方向にも同様に機能します。

あなたとメンテナーの両方が同じマシンにアカウントを持っている場合、互いのリポジトリから直接変更をプルできます。リポジトリURLを引数として受け入れるコマンドは、ローカルディレクトリ名も受け入れます。

$ git clone /path/to/repository
$ git pull /path/to/other/repository

またはssh URL

$ git clone ssh://yourhost/~you/repository

開発者の少ないプロジェクトや、いくつかのプライベートリポジトリを同期させるだけであれば、これで十分な場合があります。

しかし、より一般的な方法は、他の人が変更をプルできるように、別の公開リポジトリ(通常は別のホスト上)を維持することです。これは通常より便利であり、進行中のプライベート作業と公開されている作業をきれいに分離できます。

あなたは個人的なリポジトリで日常の作業を続けますが、定期的に個人的なリポジトリから公開リポジトリに変更を「プッシュ」することで、他の開発者がそのリポジトリからプルできるようにします。したがって、公開リポジトリを持つ別の開発者がいる状況での変更の流れは次のようになります。

		      you push
your personal repo ------------------> your public repo
      ^                                     |
      |                                     |
      | you pull                            | they pull
      |                                     |
      |                                     |
      |               they push             V
their public repo <------------------- their repo

これを行う方法については、以下のセクションで説明します。

公開リポジトリのセットアップ

あなたの個人リポジトリがディレクトリ~/projにあると仮定します。まず、リポジトリの新しいクローンを作成し、git daemonにそれが公開用であることを伝えます。

$ git clone --bare ~/proj proj.git
$ touch proj.git/git-daemon-export-ok

結果のディレクトリproj.gitには、「ベア」Gitリポジトリが含まれています。これは、.gitディレクトリの内容だけで、その周りにチェックアウトされたファイルはありません。

次に、proj.gitを公開リポジトリをホストするサーバーにコピーします。scp、rsync、または最も便利な方法を使用できます。

GitプロトコルによるGitリポジトリのエクスポート

これが推奨される方法です。

他の誰かがサーバーを管理している場合、リポジトリをどこに置くべきか、どのgit:// URLで表示されるかを教えてもらうべきです。その後、以下の「公開リポジトリへの変更のプッシュ」セクションにスキップできます。

そうでない場合は、git-daemon[1]を起動するだけで済みます。ポート9418でリッスンします。デフォルトでは、Gitディレクトリのように見え、マジックファイルgit-daemon-export-okを含む任意のディレクトリへのアクセスを許可します。git daemon引数としていくつかのディレクトリパスを渡すと、エクスポートはこれらのパスにさらに制限されます。

git daemonをinetdサービスとして実行することもできます。詳細については、git-daemon[1]のmanページを参照してください。(特に例のセクションを参照してください)。

HTTP経由でのGitリポジトリのエクスポート

Gitプロトコルはより優れたパフォーマンスと信頼性を提供しますが、Webサーバーが設定されているホストでは、HTTPエクスポートの方が設定が簡単かもしれません。

新しく作成したベアGitリポジトリをWebサーバーによってエクスポートされるディレクトリに配置し、Webクライアントが必要とする追加情報を提供するためにいくつかの調整を行うだけで済みます。

$ mv proj.git /home/you/public_html/proj.git
$ cd proj.git
$ git --bare update-server-info
$ mv hooks/post-update.sample hooks/post-update

(最後の2行の説明については、git-update-server-info[1]およびgithooks[5]を参照してください)。

proj.gitのURLを宣伝します。これで、他の誰もがそのURLからクローンまたはプルできるようになります。たとえば、次のようなコマンドラインを使用します。

$ git clone http://yourserver.com/~you/proj.git

(WebDAVを使用するもう少し洗練された設定で、HTTP経由でのプッシュも可能にするsetup-git-server-over-httpも参照してください。)

公開リポジトリへの変更のプッシュ

上記で概説した2つの手法(httpまたはgit経由でのエクスポート)は、他のメンテナーがあなたの最新の変更をフェッチすることを許可しますが、書き込みアクセスは許可しません。これは、あなたのプライベートリポジトリで作成された最新の変更で公開リポジトリを更新するために必要です。

これを行う最も簡単な方法は、git-push[1]とsshを使用することです。リモートブランチmasterを、あなたのブランチmasterの最新の状態に更新するには、次のコマンドを実行します。

$ git push ssh://yourserver.com/~you/proj.git master:master

または単純に

$ git push ssh://yourserver.com/~you/proj.git master

git fetchと同様に、これが早送りにならない場合、git pushは文句を言います。このケースの処理方法の詳細については、次のセクションを参照してください。

pushのターゲットは通常、ベアリポジトリであることに注意してください。チェックアウトされたワーキングツリーを持つリポジトリにプッシュすることもできますが、現在チェックアウトされているブランチを更新するプッシュは、混乱を防ぐためにデフォルトで拒否されます。詳細については、git-config[1]のreceive.denyCurrentBranchオプションの説明を参照してください。

git fetchと同様に、入力の手間を省くために設定オプションを設定することもできます。たとえば、

$ git remote add public-repo ssh://yourserver.com/~you/proj.git

は、.git/configに以下を追加します。

[remote "public-repo"]
	url = yourserver.com:proj.git
	fetch = +refs/heads/*:refs/remotes/example/*

これにより、次のコマンドだけで同じプッシュを実行できます。

$ git push public-repo master

詳細については、git-config[1]remote.<name>.urlbranch.<name>.remote、およびremote.<name>.pushオプションの説明を参照してください。

プッシュが失敗した場合の対処法

プッシュがリモートブランチの早送りにならない場合、次のようなエラーで失敗します。

 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to '...'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

これは、たとえば、次の状況で発生する可能性があります。

ブランチ名の前にプラス記号を付けることで、git pushを強制的に実行させることができます。

$ git push ssh://yourserver.com/~you/proj.git +master

+記号の追加に注意してください。あるいは、次のように-fフラグを使用してリモートの更新を強制することもできます。

$ git push -f ssh://yourserver.com/~you/proj.git master

通常、公開リポジトリのブランチヘッドが変更される場合、それは以前に指していたコミットの子孫を指すように変更されます。この状況でプッシュを強制すると、その慣習が破られます(履歴の書き換えに伴う問題を参照)。

しかし、これは進行中のパッチシリーズを公開する簡単な方法を必要とする人々にとって一般的な慣行であり、そのブランチをどのように管理するかを他の開発者に警告する限り、許容できる妥協策です。

他の人が同じリポジトリにプッシュする権限を持っている場合も、このようにプッシュが失敗することがあります。その場合、正しい解決策は、まず作業を更新してからプッシュを再試行することです。プルするか、フェッチに続けてリベースを行うかです。詳細については、次のセクションgitcvs-migration[7]を参照してください。

共有リポジトリのセットアップ

共同作業を行うもう1つの方法は、CVSで一般的に使用されているモデルに似たモデルを使用することです。このモデルでは、特別な権限を持つ複数の開発者がすべて1つの共有リポジトリに対してプッシュおよびプルを行います。この設定方法については、gitcvs-migration[7]を参照してください。

しかし、Gitの共有リポジトリサポートに問題はありませんが、この操作モードは一般的にお勧めできません。Gitがサポートする共同作業モード(パッチの交換と公開リポジトリからのプル)には、中央共有リポジトリよりも多くの利点があるからです。

  • Gitがパッチを素早くインポートしてマージする機能により、単一のメンテナであっても非常に高速で受信変更を処理できます。そして、それが多すぎる場合でも、git pullはそのメンテナがこの作業を他のメンテナに委譲する簡単な方法を提供し、受信変更のオプションレビューを可能にします。

  • 各開発者のリポジトリはプロジェクト履歴の完全なコピーを同じく持っているため、特別なリポジトリは存在せず、別の開発者がプロジェクトの保守を引き継ぐのは、相互の合意によるものであれ、メンテナが応答しなくなったり、一緒に作業するのが困難になったりした場合であれ、些細なことです。

  • 「コミッター」の中央グループがないということは、「誰が「参加」し、誰が「不参加」であるか」についての正式な決定の必要性が少ないことを意味します。

リポジトリのウェブブラウジングを許可する

gitweb CGIスクリプトは、Gitをインストールすることなく、プロジェクトのリビジョン、ファイルの内容、ログを簡単に参照できる方法をユーザーに提供します。RSS/Atomフィードやblame/annotationの詳細などの機能は、オプションで有効にできます。

git-instaweb[1]コマンドは、gitwebを使用してリポジトリの参照を開始する簡単な方法を提供します。instawebを使用する際のデフォルトサーバーはlighttpdです。

CGIまたはPerl対応サーバーを使用した永続的なインストールを設定する詳細な手順については、Gitソースツリー内のgitweb/INSTALLファイルおよびgitweb[1]を参照してください。

最小限の履歴を持つGitリポジトリを取得する方法

履歴を短縮したシャロークローンは、プロジェクトの最近の履歴のみに関心があり、アップストリームから完全な履歴を取得するのがコストのかかる場合に役立ちます。

シャロークローンは、git-clone[1]--depthスイッチを指定することで作成されます。深さは後でgit-fetch[1]--depthスイッチで変更するか、--unshallowで完全な履歴を復元できます。

シャロークローン内でのマージは、マージベースが最近の履歴にあれば機能します。そうでない場合、関連性のない履歴をマージするのと同様になり、大規模な競合が発生する可能性があります。この制限により、そのようなリポジトリはマージベースのワークフローには不向きになる可能性があります。

Linuxサブシステムメンテナのためのトピックブランチの管理

これは、Tony LuckがLinuxカーネルのIA64アーキテクチャのメンテナとしてGitをどのように使用しているかを説明しています。

彼は2つの公開ブランチを使用しています。

  • 「テスト」ツリー。パッチは最初にここに置かれ、他の進行中の開発と統合されたときに露出を得ることができます。このツリーは、Andrewがいつでも-mmにプルできるようになっています。

  • 「リリース」ツリー。テスト済みのパッチは、最終的な健全性チェックのためにここに移動され、Linusに(「pullしてください」リクエストを送信することで)アップストリームに送信するための手段となります。

彼はまた、それぞれ論理的なパッチのグループを含む一時的なブランチ(「トピックブランチ」)のセットを使用しています。

これを設定するには、まずLinusの公開ツリーをクローンして作業ツリーを作成します。

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git work
$ cd work

Linusのツリーは、origin/masterという名前のリモートトラッキングブランチに格納され、git-fetch[1]を使用して更新できます。git-remote[1]を使用して「リモート」を設定し、git-fetch[1]を使用してそれらを最新の状態に保つことで、他の公開ツリーを追跡できます。リポジトリとブランチを参照してください。

次に、作業するブランチを作成します。これらは、現在のorigin/masterブランチの先端から始まり、デフォルトでLinusからの変更をマージするように設定する(git-branch[1]--trackオプションを使用)必要があります。

$ git branch --track test origin/master
$ git branch --track release origin/master

これらはgit-pull[1]を使用して簡単に最新の状態に保つことができます。

$ git switch test && git pull
$ git switch release && git pull

重要な注意!これらのブランチにローカルな変更がある場合、このマージは履歴にコミットオブジェクトを作成します(ローカルな変更がない場合、Gitは単に「早送り」マージを実行します)。多くの人は、これがLinuxの履歴に作成する「ノイズ」を嫌うため、Linusにリリースブランチからプルを要求するときに、これらのノイズの多いコミットが永続的な履歴の一部になるため、releaseブランチでこれを気まぐれに行うのを避けるべきです。

いくつかの設定変数(git-config[1]を参照)により、両方のブランチを公開ツリーに簡単にプッシュできます(公開リポジトリのセットアップを参照)。

$ cat >> .git/config <<EOF
[remote "mytree"]
	url =  master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux.git
	push = release
	push = test
EOF

その後、git-push[1]を使用してテストツリーとリリースツリーの両方をプッシュできます。

$ git push mytree

または、テストブランチとリリースブランチのいずれか1つだけをプッシュすることもできます。

$ git push mytree test

または

$ git push mytree release

さて、コミュニティからのパッチを適用します。このパッチ(または関連するパッチのグループ)を保持するブランチに、短く気の利いた名前を考え、Linusのブランチの最近の安定したタグから新しいブランチを作成します。ブランチの安定したベースを選択すると、1) 関係のない、おそらく十分にテストされていない変更の混入を避け、2) git bisectを使用して問題を見つける将来のバグハンターを助けることができます。

$ git switch -c speed-up-spinlocks v2.6.35

次に、パッチを適用し、いくつかのテストを実行し、変更をコミットします。パッチが複数パートのシリーズである場合、それぞれをこのブランチに個別のコミットとして適用する必要があります。

$ ... patch ... test  ... commit [ ... patch ... test ... commit ]*

この変更の状態で満足したら、それを「テスト」ブランチにマージして公開の準備をします。

$ git switch test && git merge speed-up-spinlocks

ここで競合が発生することはほとんどありませんが、このステップに時間をかけすぎたり、アップストリームから新しいバージョンをプルしたりした場合は、発生する可能性があります。

しばらくして十分な時間が経過し、テストが完了したら、同じブランチをreleaseツリーにプルして、アップストリームに送信する準備をすることができます。ここで、各パッチ(またはパッチシリーズ)を独自のブランチに保持する価値がわかります。これは、パッチが任意の順序でreleaseツリーに移動できることを意味します。

$ git switch release && git merge speed-up-spinlocks

しばらくすると、たくさんのブランチを持つことになりますが、それぞれに選んだ名前が適切であっても、何のためのブランチなのか、どのような状態にあるのかを忘れてしまうかもしれません。特定のブランチに含まれる変更の確認には、次を使用します。

$ git log linux..branchname | git shortlog

テストブランチまたはリリースブランチに既にマージされているかどうかを確認するには、次を使用します。

$ git log test..branchname

または

$ git log release..branchname

(このブランチがまだマージされていない場合、いくつかのログエントリが表示されます。マージされている場合、出力はありません。)

パッチが大きなサイクル(テストからリリースへ移動し、Linusにプルされ、最終的にローカルのorigin/masterブランチに戻る)を完了すると、この変更のブランチはもはや必要ありません。出力が空の場合、これを検出します。

$ git log origin..branchname

この時点でブランチは削除できます。

$ git branch -d branchname

一部の変更は非常に些細なため、個別のブランチを作成してテストブランチとリリースブランチの両方にマージする必要はありません。これらの変更については、直接releaseブランチに適用し、それをtestブランチにマージするだけです。

mytreeに作業をプッシュした後、git-request-pull[1]を使用して、Linusに送信する「pullしてください」リクエストメッセージを作成できます。

$ git push mytree
$ git request-pull origin mytree release

これらすべてをさらに簡素化するスクリプトがいくつかあります。

==== update script ====
# Update a branch in my Git tree.  If the branch to be updated
# is origin, then pull from kernel.org.  Otherwise merge
# origin/master branch into test|release branch

case "$1" in
test|release)
	git checkout $1 && git pull . origin
	;;
origin)
	before=$(git rev-parse refs/remotes/origin/master)
	git fetch origin
	after=$(git rev-parse refs/remotes/origin/master)
	if [ $before != $after ]
	then
		git log $before..$after | git shortlog
	fi
	;;
*)
	echo "usage: $0 origin|test|release" 1>&2
	exit 1
	;;
esac
==== merge script ====
# Merge a branch into either the test or release branch

pname=$0

usage()
{
	echo "usage: $pname branch test|release" 1>&2
	exit 1
}

git show-ref -q --verify -- refs/heads/"$1" || {
	echo "Can't see branch <$1>" 1>&2
	usage
}

case "$2" in
test|release)
	if [ $(git log $2..$1 | wc -c) -eq 0 ]
	then
		echo $1 already merged into $2 1>&2
		exit 1
	fi
	git checkout $2 && git pull . $1
	;;
*)
	usage
	;;
esac
==== status script ====
# report on status of my ia64 Git tree

gb=$(tput setab 2)
rb=$(tput setab 1)
restore=$(tput setab 9)

if [ `git rev-list test..release | wc -c` -gt 0 ]
then
	echo $rb Warning: commits in release that are not in test $restore
	git log test..release
fi

for branch in `git show-ref --heads | sed 's|^.*/||'`
do
	if [ $branch = test -o $branch = release ]
	then
		continue
	fi

	echo -n $gb ======= $branch ====== $restore " "
	status=
	for ref in test release origin/master
	do
		if [ `git rev-list $ref..$branch | wc -c` -gt 0 ]
		then
			status=$status${ref:0:1}
		fi
	done
	case $status in
	trl)
		echo $rb Need to pull into test $restore
		;;
	rl)
		echo "In test"
		;;
	l)
		echo "Waiting for linus"
		;;
	"")
		echo $rb All done $restore
		;;
	*)
		echo $rb "<$status>" $restore
		;;
	esac
	git log origin/master..$branch | git shortlog
done

履歴の書き換えとパッチシリーズの維持

通常、コミットはプロジェクトに追加されるだけで、削除されたり置き換えられたりすることはありません。Gitはこの仮定に基づいて設計されており、これに違反すると、Gitのマージ機構(たとえば)が誤った動作をします。

しかし、この仮定に違反することが有用な状況があります。

完璧なパッチシリーズの作成

あなたが大規模プロジェクトの貢献者であり、複雑な機能を加えたいとします。そして、他の開発者があなたの変更を簡単に読み、それらが正しいことを確認し、各変更を行った理由を理解できるように提示したいとします。

すべての変更を単一のパッチ(またはコミット)として提示した場合、彼らは一度に消化するには多すぎると感じるかもしれません。

間違い、修正、行き詰まりを含んだあなたの作業の全履歴を提示した場合、彼らは圧倒されるかもしれません。

したがって、理想は通常、次のような一連のパッチを作成することです。

  1. 各パッチは順番に適用できます。

  2. 各パッチには、単一の論理的な変更と、変更を説明するメッセージが含まれています。

  3. パッチにリグレッションはありません。シリーズの初期部分を適用した後でも、結果のプロジェクトはコンパイルされ、機能し、以前にはなかったバグがありません。

  4. 完全なシリーズは、あなた自身の(おそらくもっとごちゃごちゃした!)開発プロセスと同じ最終結果を生み出します。

これを手助けするいくつかのツールを紹介し、それらの使い方を説明し、その後、履歴を書き換えることによって発生する可能性のあるいくつかの問題を説明します。

git rebase を使用してパッチシリーズを最新に保つ

リモートトラッキングブランチorigin上にmyworkブランチを作成し、その上にいくつかのコミットを作成したとします。

$ git switch -c mywork origin
$ vi file.txt
$ git commit
$ vi otherfile.txt
$ git commit
...

myworkにマージは行われていないため、origin上に単純な線形パッチシーケンスがあるだけです。

 o--o--O <-- origin
        \
	 a--b--c <-- mywork

アップストリームプロジェクトでさらに興味深い作業が行われ、originが進んでいます。

 o--o--O--o--o--o <-- origin
        \
         a--b--c <-- mywork

この時点で、pullを使用して変更をマージし直すことができます。結果として、次のような新しいマージコミットが作成されます。

 o--o--O--o--o--o <-- origin
        \        \
         a--b--c--m <-- mywork

しかし、myworkの履歴をマージなしの単純なコミットシリーズに保ちたい場合は、代わりにgit-rebase[1]を使用することを選択できます。

$ git switch mywork
$ git rebase origin

これにより、myworkから各コミットが削除され、一時的にパッチとして保存され(.git/rebase-applyというディレクトリに)、myworkがoriginの最新バージョンを指すように更新され、その後、保存された各パッチが新しいmyworkに適用されます。結果は次のようになります。

 o--o--O--o--o--o <-- origin
		 \
		  a'--b'--c' <-- mywork

その過程で、競合が発見されることがあります。その場合、競合を修正できるよう停止します。競合を修正した後、git addを使用してそれらの内容でインデックスを更新し、git commitを実行する代わりに、単に次を実行します。

$ git rebase --continue

するとGitは残りのパッチの適用を続けます。

いつでも--abortオプションを使用してこのプロセスを中止し、myworkをリベースを開始する前の状態に戻すことができます。

$ git rebase --abort

ブランチ内の多くのコミットを並べ替えたり編集したりする必要がある場合、git rebase -iを使用する方が簡単かもしれません。これは、コミットを並べ替えたり、まとめたり、リベース中に個別の編集のためにマークしたりすることができます。詳細についてはインタラクティブリベースの使用を、代替案についてはパッチシリーズの並べ替えまたは選択を参照してください。

単一のコミットの書き換え

履歴を書き換えて間違いを修正するで、次を使用して最新のコミットを置き換えることができることを確認しました。

$ git commit --amend

これにより、古いコミットは変更を組み込んだ新しいコミットに置き換えられ、最初に古いコミットメッセージを編集する機会が与えられます。これは、最後のコミットのタイプミスを修正したり、不適切にステージングされたコミットのパッチ内容を調整したりするのに役立ちます。

履歴の深い部分にあるコミットを修正する必要がある場合は、インタラクティブリベースのedit命令を使用できます。

パッチシリーズの並べ替えまたは選択

履歴の深い部分にあるコミットを編集したい場合があります。一つの方法は、git format-patchを使用して一連のパッチを作成し、その後、パッチの前の状態にリセットすることです。

$ git format-patch origin
$ git reset --hard origin

その後、git-am[1]で再度適用する前に、必要に応じてパッチを修正、並べ替え、または削除します。

$ git am *.patch

インタラクティブリベースの使用

インタラクティブリベースを使用してパッチシリーズを編集することもできます。これはformat-patchを使用してパッチシリーズを並べ替えるのと同じなので、一番好きなインターフェースを使用してください。

現在のHEADを、そのまま残したい最後のコミットでリベースします。たとえば、最後の5つのコミットを並べ替えたい場合は、次を使用します。

$ git rebase -i HEAD~5

これにより、リベースを実行するための手順リストがエディタで開かれます。

pick deadbee The oneline of this commit
pick fa1afe1 The oneline of the next commit
...

# Rebase c0ffeee..deadbee onto c0ffeee
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

コメントで説明されているように、リストを編集することでコミットの順序を変更したり、それらをまとめたり、コミットメッセージを編集したりできます。満足したら、リストを保存してエディタを閉じると、リベースが開始されます。

リベースは、pickeditに置き換えられた箇所、またはリストのステップが機械的に競合を解決できず、あなたの助けが必要な箇所で停止します。編集や競合の解決が完了したら、git rebase --continueで続行できます。状況が複雑になりすぎていると感じた場合は、いつでもgit rebase --abortで中止できます。リベースが完了した後でも、reflogを使用すれば元のブランチを回復できます。

手順と追加のヒントの詳細については、git-rebase[1]の「INTERACTIVE MODE」セクションを参照してください。

その他のツール

StGitなど、パッチシリーズの管理を目的とした多くのツールがあります。これらはこのマニュアルの範囲外です。

履歴の書き換えに伴う問題

ブランチの履歴を書き換える際の主な問題はマージに関係しています。誰かがあなたのブランチをフェッチして自分のブランチにマージし、次のような結果になったと仮定します。

 o--o--O--o--o--o <-- origin
        \        \
         t--t--t--m <-- their branch:

次に、最後の3つのコミットを修正したとします。

	 o--o--o <-- new head of origin
	/
 o--o--O--o--o--o <-- old head of origin

このすべての履歴を1つのリポジトリでまとめて調べると、次のようになります。

	 o--o--o <-- new head of origin
	/
 o--o--O--o--o--o <-- old head of origin
        \        \
         t--t--t--m <-- their branch:

Gitは新しいヘッドが古いヘッドの更新版であることを知る方法がありません。この状況は、2人の開発者が古いヘッドと新しいヘッドで並行して独立して作業した場合とまったく同じように扱われます。この時点で、誰かが新しいヘッドを自分のブランチにマージしようとすると、Gitは古いものを新しいものに置き換えようとするのではなく、2つの開発ライン(古いものと新しいもの)をマージしようとします。結果は予期しないものになる可能性があります。

履歴が書き換えられたブランチを公開することを選択することもでき、他の人がそれらのブランチを取得して調べたりテストしたりするのに役立つかもしれませんが、彼らはそのようなブランチを自分の作業にプルしようとすべきではありません。

適切なマージをサポートする真の分散開発では、公開されたブランチは決して書き換えられるべきではありません。

マージコミットのバイセクトが線形履歴のバイセクトよりも難しい理由

git-bisect[1]コマンドは、マージコミットを含む履歴を正しく処理します。ただし、見つかったコミットがマージコミットである場合、ユーザーは通常よりもそのコミットが問題を引き起こした理由を特定するために苦労する必要があるかもしれません。

この履歴を想像してみてください。

      ---Z---o---X---...---o---A---C---D
          \                       /
           o---o---Y---...---o---B

開発の上流ラインで、Zに存在する関数の意味がコミットXで変更されたとします。ZからAに至るコミットは、関数の実装とZに存在するすべての呼び出しサイト、および追加する新しい呼び出しサイトの両方を変更して一貫性を保ちます。Aにはバグはありません。

その間に、開発の下流ラインで誰かがコミットYでその関数の新しい呼び出しサイトを追加したとします。ZからBに至るコミットはすべてその関数の古いセマンティクスを仮定しており、呼び出し元と呼び出し先は互いに一貫しています。Bにもバグはありません。

さらに、2つの開発ラインがCでクリーンにマージされ、競合解決は不要であると仮定します。

それにもかかわらず、Cのコードは壊れています。なぜなら、開発の下流ラインで追加された呼び出し元が、開発の上流ラインで導入された新しいセマンティクスに変換されていないからです。したがって、Dが不良でZが良好であり、git-bisect[1]がCを原因として特定したことだけを知っている場合、その問題がこのセマンティクスの変更によるものであることをどのようにして見つけ出すのでしょうか?

git bisectの結果が非マージコミットである場合、通常はそのコミットのみを調べることで問題を特定できるはずです。開発者は、変更を小さな自己完結型のコミットに分割することで、これを容易にすることができます。ただし、上記のケースでは、問題がいずれの単一コミットの検査からも明らかではないため、これは役立ちません。代わりに、開発の全体的な視点が必要となります。さらに悪いことに、問題のある関数のセマンティクスの変更は、開発の上流ラインにおける変更のほんの一部にすぎないかもしれません。

一方、Cでマージする代わりに、ZからBまでの履歴をAの上にリベースした場合、次のような線形履歴が得られます。

    ---Z---o---X--...---o---A---o---o---Y*--...---o---B*--D*

ZとD*の間を二分すると、単一の犯人コミットY*にヒットし、Y*が壊れた理由を理解するのはおそらくより簡単でしょう。

この理由もあって、多くの経験豊富なGitユーザーは、マージ中心のプロジェクトで作業している場合でも、公開する前に最新のアップストリームバージョンに対してリベースすることで履歴を線形に保っています。

高度なブランチ管理

個別のブランチのフェッチ

git-remote[1]を使用する代わりに、一度に1つのブランチだけを更新し、任意の名前でローカルに保存することもできます。

$ git fetch origin todo:my-todo-work

最初の引数originは、Gitに最初にクローンしたリポジトリからフェッチするように指示するだけです。2番目の引数は、Gitにリモートリポジトリからtodoという名前のブランチをフェッチし、それをrefs/heads/my-todo-workという名前でローカルに保存するように指示します。

他のリポジトリからブランチをフェッチすることもできます。したがって、

$ git fetch git://example.com/proj.git master:example-master

は、example-masterという新しいブランチを作成し、指定されたURLのリポジトリからmasterというブランチをそこに格納します。既にexample-masterというブランチがある場合、example.comのmasterブランチで指定されたコミットにfast-forwardしようとします。より詳しく説明すると、

git fetchとfast-forwards

前の例では、既存のブランチを更新する際に、git fetchは、リモートブランチの最新コミットが、ブランチのコピーを新しいコミットを指すように更新する前に、そのブランチのコピーの最新コミットの子孫であることを確認します。Gitはこのプロセスをfast-forwardと呼びます。

fast-forwardは次のようなものです。

 o--o--o--o <-- old head of the branch
           \
            o--o--o <-- new head of the branch

場合によっては、新しいヘッドが実際に古いヘッドの子孫ではないことがあります。例えば、開発者が重大な間違いを犯したことに気づき、巻き戻すことを決定し、次のような状況になった場合です。

 o--o--o--o--a--b <-- old head of the branch
           \
            o--o--o <-- new head of the branch

この場合、git fetchは失敗し、警告を出力します。

その場合でも、次のセクションで説明するように、Gitに新しいヘッドへの更新を強制することができます。ただし、上記の状況では、コミットabを指す独自の参照をすでに作成していない限り、それらのコミットを失う可能性があることに注意してください。

git fetchに非fast-forward更新を強制する

ブランチの新しいヘッドが古いヘッドの子孫でないためにgit fetchが失敗した場合、次のようにして更新を強制できます。

$ git fetch git://example.com/proj.git +master:refs/remotes/example/master

+記号の追加に注意してください。または、次のように-fフラグを使用して、フェッチされたすべてのブランチの更新を強制することもできます。

$ git fetch -f origin

前のセクションで見たように、古いバージョンのexample/masterが指していたコミットが失われる可能性があることに注意してください。

リモートトラッキングブランチの設定

上記で、originは、最初にクローンしたリポジトリを参照するためのショートカットにすぎないことを確認しました。この情報はGitの構成変数に保存されており、git-config[1]を使用して確認できます。

$ git config -l
core.repositoryformatversion=0
core.filemode=true
core.logallrefupdates=true
remote.origin.url=git://git.kernel.org/pub/scm/git/git.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master

他にも頻繁に使用するリポジトリがある場合、入力の手間を省くために同様の構成オプションを作成できます。例えば、

$ git remote add example git://example.com/proj.git

は、.git/configに以下を追加します。

[remote "example"]
	url = git://example.com/proj.git
	fetch = +refs/heads/*:refs/remotes/example/*

また、上記の設定は、git-remote[1]を使用する代わりに、.git/configファイルを直接編集することでも実行できることに注意してください。

リモートを設定した後、次の3つのコマンドは同じことをします。

$ git fetch git://example.com/proj.git +refs/heads/*:refs/remotes/example/*
$ git fetch example +refs/heads/*:refs/remotes/example/*
$ git fetch example

上記の構成オプションの詳細についてはgit-config[1]を、refspec構文の詳細についてはgit-fetch[1]を参照してください。

Gitの概念

Gitは、シンプルだが強力な少数のアイデアに基づいて構築されています。それらを理解しなくても物事を成し遂げることは可能ですが、理解していればGitがはるかに直感的になります。

最も重要なオブジェクトデータベースインデックスから始めます。

オブジェクトデータベース

履歴の理解:コミットですでに見たように、すべてのコミットは40桁の「オブジェクト名」で保存されます。実際、プロジェクトの履歴を表すのに必要なすべての情報は、そのような名前を持つオブジェクトに保存されます。各場合において、名前はオブジェクトの内容のSHA-1ハッシュを取ることによって計算されます。SHA-1ハッシュは暗号学的ハッシュ関数です。これは私たちにとって、同じ名前を持つ2つの異なるオブジェクトを見つけることは不可能であることを意味します。これには多くの利点があります。その中でも、

  • Gitは、名前を比較するだけで、2つのオブジェクトが同一であるかどうかをすばやく判断できます。

  • オブジェクト名はすべてのリポジトリで同じ方法で計算されるため、2つのリポジトリに保存された同じ内容は常に同じ名前で保存されます。

  • Gitは、オブジェクトを読み取るときにエラーを検出できます。オブジェクトの名前がその内容のSHA-1ハッシュとまだ一致するかどうかをチェックするからです。

(オブジェクトの書式設定とSHA-1計算の詳細については、オブジェクトストレージ形式を参照してください。)

オブジェクトには、「blob」、「tree」、「commit」、「tag」の4つの異なるタイプがあります。

  • 「blob」オブジェクトは、ファイルデータを格納するために使用されます。

  • 「tree」オブジェクトは、1つ以上の「blob」オブジェクトをディレクトリ構造に結び付けます。さらに、ツリーオブジェクトは他のツリーオブジェクトを参照できるため、ディレクトリ階層を作成します。

  • 「commit」オブジェクトは、そのようなディレクトリ階層をリビジョンの有向非巡回グラフにまとめます。各コミットは、コミット時のディレクトリ階層を指定するちょうど1つのツリーのオブジェクト名を含みます。さらに、コミットは、そのディレクトリ階層に到達した経緯の履歴を記述する「親」コミットオブジェクトを参照します。

  • 「タグ」オブジェクトは、他のオブジェクトを象徴的に識別し、署名するために使用できます。これには、別のオブジェクトのオブジェクト名とタイプ、シンボル名(もちろん!)、そしてオプションで署名が含まれます。

オブジェクトの種類をもう少し詳しく

コミットオブジェクト

「コミット」オブジェクトは、ツリーの物理的な状態と、そこに到達した経緯および理由の説明を結びつけます。git-show[1]またはgit-log[1]--pretty=rawオプションを使用して、お気に入りのコミットを調べてみましょう。

$ git show -s --pretty=raw 2be7fcb476
commit 2be7fcb4764f2dbcee52635b91fedb1b3dcf7ab4
tree fb3a8bdd0ceddd019615af4d57a53f43d8cee2bf
parent 257a84d9d02e90447b149af58b271c19405edb6a
author Dave Watson <dwatson@mimvista.com> 1187576872 -0400
committer Junio C Hamano <gitster@pobox.com> 1187591163 -0700

    Fix misspelling of 'suppress' in docs

    Signed-off-by: Junio C Hamano <gitster@pobox.com>

ご覧のとおり、コミットは次の要素で定義されます。

  • tree:特定の時点でのディレクトリの内容を表す、ツリーオブジェクト(以下に定義)のSHA-1名。

  • parent(s):プロジェクト履歴の直前のステップを表すコミットのSHA-1名。上記の例には1つの親があります。マージコミットには複数の親がある場合があります。親を持たないコミットは「ルート」コミットと呼ばれ、プロジェクトの初期リビジョンを表します。各プロジェクトには少なくとも1つのルートが必要です。プロジェクトは複数のルートを持つことも可能ですが、一般的ではありません(または必ずしも良いアイデアではありません)。

  • author:この変更を担当した人の名前とその日付。

  • committer:実際にコミットを作成した人の名前とその日付。これは作者と異なる場合があります。例えば、作者がパッチを書き、それをコミット作成者が使用するために電子メールで送信した場合などです。

  • このコミットを説明するコメント。

コミット自体は、実際に何が変更されたかについての情報を何も含んでいないことに注意してください。すべての変更は、このコミットが参照するツリーの内容と、その親に関連付けられたツリーの内容を比較することによって計算されます。特に、Gitはファイルの名前変更を明示的に記録しようとしませんが、パスが変更された同じファイルデータが存在するケースは名前変更を示唆すると識別できます(例えば、git-diff[1]-Mオプションを参照)。

コミットは通常、git-commit[1]によって作成されます。これは、親が通常現在のHEADであり、ツリーが現在インデックスに格納されている内容から取得されるコミットを作成します。

ツリーオブジェクト

万能なgit-show[1]コマンドもツリーオブジェクトの調査に使用できますが、git-ls-tree[1]はより詳細な情報を提供します。

$ git ls-tree fb3a8bdd0ce
100644 blob 63c918c667fa005ff12ad89437f2fdc80926e21c    .gitignore
100644 blob 5529b198e8d14decbe4ad99db3f7fb632de0439d    .mailmap
100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3    COPYING
040000 tree 2fb783e477100ce076f6bf57e4a6f026013dc745    Documentation
100755 blob 3c0032cec592a765692234f1cba47dfdcc3a9200    GIT-VERSION-GEN
100644 blob 289b046a443c0647624607d471289b2c7dcd470b    INSTALL
100644 blob 4eb463797adc693dc168b926b6932ff53f17d0b1    Makefile
100644 blob 548142c327a6790ff8821d67c2ee1eff7a656b52    README
...

ご覧のとおり、ツリーオブジェクトにはエントリのリストが含まれており、それぞれモード、オブジェクトタイプ、SHA-1名、名前を持ち、名前でソートされています。これは単一のディレクトリツリーの内容を表します。

オブジェクトの種類は、ファイルの内容を表すblob、またはサブディレクトリの内容を表す別のツリーである場合があります。ツリーとblobは、他のすべてのオブジェクトと同様に、その内容のSHA-1ハッシュによって命名されるため、2つのツリーは、その内容(再帰的に、すべてのサブディレクトリの内容を含む)が同一である場合にのみ同じSHA-1名を持ちます。これにより、Gitは、オブジェクト名が同一のエントリを無視できるため、2つの関連するツリーオブジェクト間の違いをすばばやく判断できます。

(注:サブモジュールが存在する場合、ツリーはコミットをエントリとして持つこともあります。詳細はサブモジュールを参照してください。)

ファイルにはすべてモード644または755が付いていることに注意してください。Gitは実際には実行可能ビットのみに注意を払っています。

Blobオブジェクト

git-show[1]を使用してblobの内容を調べることができます。たとえば、上記のツリーのCOPYINGのエントリにあるblobを見てみましょう。

$ git show 6ff87c4664

 Note that the only valid version of the GPL as far as this project
 is concerned is _this_ particular version of the license (ie v2, not
 v2.2 or v3.x or whatever), unless explicitly otherwise stated.
...

「blob」オブジェクトは、バイナリデータのかたまりにすぎません。他のものを参照したり、いかなる属性も持ったりしません。

blobは完全にそのデータによって定義されるため、ディレクトリツリー内(またはリポジトリの異なるバージョン内)の2つのファイルが同じ内容を持つ場合、それらは同じblobオブジェクトを共有します。オブジェクトはディレクトリツリー内の場所とは完全に独立しており、ファイルの名前を変更しても、そのファイルに関連付けられたオブジェクトは変更されません。

任意のツリーまたはブロブオブジェクトは、git-show[1]と<revision>:<path>構文を使用して調べることができることに注意してください。これは、現在チェックアウトされていないツリーの内容を参照するのに役立つ場合があります。

信頼

あるソースからblobのSHA-1名を受け取り、その内容を別の(おそらく信頼できない)ソースから受け取ったとしても、SHA-1名が一致する限り、その内容が正しいと信頼することができます。これは、SHA-1が、同じハッシュを生成する異なる内容を見つけることが不可能になるように設計されているためです。

同様に、トップレベルのツリーオブジェクトのSHA-1名を信頼するだけで、それが参照するディレクトリ全体の内容を信頼できます。また、信頼できるソースからコミットのSHA-1名を受け取った場合、そのコミットの親を介して到達可能なコミットの履歴全体と、それらのコミットが参照するツリーのすべての内容を簡単に検証できます。

したがって、システムに真の信頼を導入するために必要なのは、トップレベルコミットの名前を含む1つの特別なメモにデジタル署名することだけです。あなたのデジタル署名は、そのコミットを信頼していることを他の人に示し、コミットの履歴の不変性は、履歴全体を信頼できることを他の人に伝えます。

つまり、トップコミットの名前(SHA-1ハッシュ)を人々に伝える単一の電子メールを送信し、GPG/PGPのようなものを使ってその電子メールにデジタル署名するだけで、アーカイブ全体を簡単に検証できます。

これを支援するために、Gitはタグオブジェクトも提供しています...

タグオブジェクト

タグオブジェクトには、オブジェクト、オブジェクトタイプ、タグ名、タグを作成した人物(「タガー」)の名前、および署名を含むメッセージが含まれます。git-cat-file[1]を使用すると、これらを確認できます。

$ git cat-file tag v1.5.0
object 437b1b20df4b356c9342dac8d38849f24ef44f27
type commit
tag v1.5.0
tagger Junio C Hamano <junkio@cox.net> 1171411200 +0000

GIT 1.5.0
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)

iD8DBQBF0lGqwMbZpPMRm5oRAuRiAJ9ohBLd7s2kqjkKlq1qqC57SbnmzQCdG4ui
nLE/L9aUXdWeTFPron96DLA=
=2E+0
-----END PGP SIGNATURE-----

タグオブジェクトの作成および検証方法については、git-tag[1]コマンドを参照してください。(git-tag[1]は「軽量タグ」の作成にも使用できます。これはタグオブジェクトではなく、単にrefs/tags/で始まる名前の単純な参照です。)

Gitがオブジェクトを効率的に保存する方法:パックファイル

新しく作成されたオブジェクトは、最初はオブジェクトのSHA-1ハッシュにちなんで名付けられたファイル(.git/objectsに保存)に作成されます。

残念ながら、このシステムはプロジェクトに多くのオブジェクトがある場合、非効率になります。古いプロジェクトでこれを試してください。

$ git count-objects
6930 objects, 47620 kilobytes

最初の数値は個別のファイルに保持されているオブジェクトの数です。2番目の数値は、それらの「ルーズ」オブジェクトが占めるスペースの量です。

これらのルーズオブジェクトを「パックファイル」に移動することで、スペースを節約し、Gitを高速化できます。パックファイルは、オブジェクトのグループを効率的な圧縮形式で保存します。パックファイルの形式の詳細については、gitformat-pack[5]で確認できます。

ルーズオブジェクトをパックに入れるには、git repackを実行するだけです。

$ git repack
Counting objects: 6020, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6020/6020), done.
Writing objects: 100% (6020/6020), done.
Total 6020 (delta 4070), reused 0 (delta 0)

これは、現在パックされていないすべてのオブジェクトを含む単一の「パックファイル」を.git/objects/pack/に作成します。その後、次を実行できます。

$ git prune

パックに含まれるようになった「ルーズ」オブジェクトを削除します。これにより、参照されていないオブジェクトも削除されます(たとえば、git resetを使用してコミットを削除した場合などに作成される可能性があります)。.git/objectsディレクトリを確認するか、次を実行することで、ルーズオブジェクトがなくなったことを確認できます。

$ git count-objects
0 objects, 0 kilobytes

オブジェクトファイルがなくなっても、それらのオブジェクトを参照するコマンドは以前とまったく同じように機能します。

git-gc[1]コマンドは、パック、プルーニングなどを行いますので、通常はこれだけを実行すれば良いでしょう。

ぶら下がりオブジェクト

git-fsck[1]コマンドは、ぶら下がりオブジェクトについて文句を言うことがあります。これらは問題ではありません。

ぶら下がりオブジェクトの最も一般的な原因は、ブランチをリベースしたか、リベースされたブランチから誰かからプルしたことです。—履歴の書き換えとパッチシリーズの維持を参照してください。その場合、元のブランチの古いヘッドはまだ存在し、それが指していたすべてのものも存在します。ブランチポインタ自体は、別のものに置き換えたため、存在しません。

ぶら下がりオブジェクトが発生する他の状況もあります。例えば、「ぶら下がりブロブ」は、ファイルをgit addしたが、実際にコミットして全体の一部にする前に、そのファイル内の何かを変更し、その**更新された**ものをコミットした場合に発生する可能性があります。—元々追加した古い状態はどのコミットやツリーからも指されなくなり、ぶら下がりブロブオブジェクトになります。

同様に、「ort」マージ戦略が実行され、交差マージがあり、複数のマージベース(これはかなり珍しいですが、発生します)が見つかった場合、一時的な中間ツリー(交差するマージが多く、マージベースが2つ以上ある場合はさらに多くなる可能性もあります)が一時的な内部マージベースとして生成されます。これらは実際のオブジェクトですが、最終結果はそれらを指し示さないため、リポジトリに「ぶら下がり」として残ります。

一般的に、ぶら下がりオブジェクトは心配する必要はありません。それらは非常に役立つことさえあります。何かを台無しにした場合、ぶら下がりオブジェクトは古いツリーを復元する方法になり得ます(たとえば、リベースを実行し、実際にはそうしたくなかったと気づいた場合—持っているぶら下がりオブジェクトを見て、ヘッドを古いぶら下がり状態にリセットすることを決定できます)。

コミットの場合は、単に次を使用できます。

$ gitk <dangling-commit-sha-goes-here> --not --all

これは、指定されたコミットから到達可能だが、どのブランチ、タグ、または他の参照からも到達できないすべての履歴を要求します。興味がある場合は、いつでも新しい参照を作成できます。例:

$ git branch recovered-branch <dangling-commit-sha-goes-here>

ブロブとツリーについては同じことはできませんが、それでも調べることができます。ただ次を実行できます。

$ git show <dangling-blob/tree-sha-goes-here>

これにより、blobの内容(またはツリーの場合、そのディレクトリのlsが基本的に何であったか)が表示され、そのぶら下がりオブジェクトを残した操作が何であったかについて、ある程度のアイデアが得られるかもしれません。

通常、ぶら下がりブロブやツリーはあまり興味深いものではありません。それらはほとんど常に、中間マージベースであるか(ブロブには、手動で修正した競合するマージがあった場合、マージの競合マーカーが含まれていることさえあります)、またはgit fetchを^Cなどで中断し、**一部の**新しいオブジェクトがオブジェクトデータベースに残ったものの、ぶら下がっていて役に立たない状態になったかのどちらかの結果です。

いずれにせよ、ぶら下がっている状態に興味がないと確信したら、到達不可能なすべてのオブジェクトを剪定するだけです。

$ git prune

そしてそれらは消えます。(git pruneは静止したリポジトリでのみ実行すべきです。ファイルシステムのfsckリカバリのようなもので、ファイルシステムがマウントされている間はそれを行いたくありません。git pruneは、リポジトリへの同時アクセスがある場合でも害を及ぼさないように設計されていますが、混乱を招くような、あるいは恐ろしいメッセージを受け取るかもしれません。)

リポジトリ破損からの回復

設計上、Gitは信頼されたデータを注意深く扱います。しかし、Git自体にバグがない場合でも、ハードウェアやオペレーティングシステムのエラーによってデータが破損する可能性があります。

そのような問題に対する最初の防御策はバックアップです。クローンを使用するか、cp、tar、またはその他のバックアップメカニズムを使用するだけで、Gitディレクトリをバックアップできます。

最後の手段として、破損したオブジェクトを検索し、手動で置き換えを試みることができます。このプロセスでさらに破損させる可能性があるので、試みる前にリポジトリをバックアップしてください。

問題が単一の欠落または破損したブロブであると仮定します。これは時々解決可能な問題です。(欠落したツリー、特にコミットを回復するのは**はるかに**困難です)。

開始する前に、破損があることを確認し、git-fsck[1]でどこにあるかを特定してください。これは時間がかかる場合があります。

出力が次のようになると仮定します。

$ git fsck --full --no-dangling
broken link from    tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
              to    blob 4b9458b3786228369c63936db65827de3cc06200
missing blob 4b9458b3786228369c63936db65827de3cc06200

これで、blob 4b9458b3 が見つからず、tree 2d9263c6 がそれを指していることがわかりました。もしその欠落したblobオブジェクトのコピーを1つだけでも見つけることができれば、おそらく他のリポジトリで、それを.git/objects/4b/9458b3...に移動するだけで完了です。見つけられないとしましょう。git-ls-tree[1]でそれを指しているツリーをまだ調べることができ、次のような出力になるかもしれません。

$ git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8	.gitignore
100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883	.mailmap
100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c	COPYING
...
100644 blob 4b9458b3786228369c63936db65827de3cc06200	myfile
...

これで、欠落したブロブがmyfileという名前のファイルのデータであることがわかりました。そして、ディレクトリも特定できる可能性が高いです。—somedirectoryにあるとしましょう。もし運が良ければ、欠落したコピーは、作業ツリーのsomedirectory/myfileでチェックアウトしたコピーと同じかもしれません。それが正しいかどうかは、git-hash-object[1]でテストできます。

$ git hash-object -w somedirectory/myfile

これはsomedirectory/myfileの内容を持つblobオブジェクトを作成して保存し、そのオブジェクトのSHA-1を出力します。もし非常に運が良ければ、それが4b9458b3786228369c63936db65827de3cc06200である可能性があり、その場合は正解であり、破損は修正されます!

そうでなければ、さらに情報が必要です。ファイルのどのバージョンが失われたかをどうやって判断するのでしょうか?

これを行う最も簡単な方法は次のとおりです。

$ git log --raw --all --full-history -- somedirectory/myfile

生の出力を要求しているため、次のようなものが得られます。

commit abc
Author:
Date:
...
:100644 100644 4b9458b newsha M somedirectory/myfile


commit xyz
Author:
Date:

...
:100644 100644 oldsha 4b9458b M somedirectory/myfile

これは、ファイルの直後のバージョンが「newsha」であり、直前のバージョンが「oldsha」であったことを示しています。また、oldshaから4b9458bへの変更、および4b9458bからnewshaへの変更に伴うコミットメッセージもわかります。

十分小さい変更をコミットしていれば、これで中間状態4b9458bの内容を再構築する良い機会が得られるかもしれません。

それができるなら、次のようにして欠落したオブジェクトを再作成できます。

$ git hash-object -w <recreated-file>

そして、あなたのリポジトリは再び正常に戻ります!

(ところで、fsckを無視して、次を実行することから始めることもできたでしょう。

$ git log --raw --all

そして、その全体から欠落しているオブジェクト(4b9458b)のshaを探すだけでした。それはあなた次第です。—Gitには多くの情報が**あります**が、特定のブロブバージョンが1つ欠落しているだけです。

インデックス

インデックスはバイナリファイル(通常は.git/indexに保持)で、パス名のソートされたリストが含まれており、各パス名にはパーミッションとブロブオブジェクトのSHA-1が記載されています。git-ls-files[1]はインデックスの内容を表示できます。

$ git ls-files --stage
100644 63c918c667fa005ff12ad89437f2fdc80926e21c 0	.gitignore
100644 5529b198e8d14decbe4ad99db3f7fb632de0439d 0	.mailmap
100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0	COPYING
100644 a37b2152bd26be2c2289e1f57a292534a51a93c7 0	Documentation/.gitignore
100644 fbefe9a45b00a54b58d94d06eca48b03d40a50e0 0	Documentation/Makefile
...
100644 2511aef8d89ab52be5ec6a5e46236b4b6bcd07ea 0	xdiff/xtypes.h
100644 2ade97b2574a9f77e7ae4002a4e07a6a38e46d07 0	xdiff/xutils.c
100644 d5de8292e05e7c36c4b68857c1cf9855e3d2f70a 0	xdiff/xutils.h

古いドキュメントでは、インデックスが「現在のディレクトリキャッシュ」または単に「キャッシュ」と呼ばれている場合があります。これには3つの重要な特性があります。

  1. インデックスには、単一の(一意に決定された)ツリーオブジェクトを生成するために必要なすべての情報が含まれています。

    例えば、git-commit[1]を実行すると、インデックスからこのツリーオブジェクトを生成し、オブジェクトデータベースに格納し、新しいコミットに関連付けられたツリーオブジェクトとして使用します。

  2. インデックスは、定義されたツリーオブジェクトと作業ツリーの間を高速で比較できます。

    これは、各エントリにいくつかの追加データ(最終変更時刻など)を保存することで実現されます。このデータは上記には表示されず、作成されたツリーオブジェクトにも保存されませんが、作業ディレクトリのどのファイルがインデックスに保存されたものと異なるかをすばやく判断するために使用でき、その結果、変更を探すためにそのようなファイルのすべてのデータを読み取る必要がなくなります。

  3. 異なるツリーオブジェクト間のマージの競合に関する情報を効率的に表現でき、各パス名に、それらの間に3方向マージを作成できるような十分な情報が関連付けられるようになります。

    マージ中の競合解決のヘルプを得るで、マージ中にインデックスが単一ファイルの複数のバージョン(「ステージ」と呼ばれる)を格納できることを確認しました。上記のgit-ls-files[1]出力の3列目はステージ番号であり、マージ競合のあるファイルでは0以外の値をとります。

したがって、インデックスは、作業中のツリーで満たされる一種の一時的なステージング領域です。

インデックスを完全に削除しても、それが記述するツリーの名前が分かっている限り、通常は情報が失われることはありません。

サブモジュール

大規模なプロジェクトは、多くの場合、より小さな自己完結型のモジュールで構成されます。例えば、組み込みLinuxディストリビューションのソースツリーには、ディストリビューション内のすべてのソフトウェアにいくつかのローカル変更が加えられたものが含まれるでしょう。ムービープレーヤーは、特定の既知の動作するバージョンのデコードライブラリに対してビルドする必要があるかもしれません。いくつかの独立したプログラムがすべて同じビルドスクリプトを共有しているかもしれません。

集中型バージョン管理システムでは、これは多くの場合、すべてのモジュールを1つの単一リポジトリに含めることによって実現されます。開発者は、すべてのモジュールまたは作業する必要があるモジュールのみをチェックアウトできます。ファイルを移動したり、APIや翻訳を更新したりしながら、単一のコミットで複数のモジュールにわたってファイルを変更することもできます。

Gitは部分的なチェックアウトを許可しないため、このアプローチをGitで複製すると、開発者は関心のないモジュールのローカルコピーを保持することを強制されます。非常に大きなチェックアウトでのコミットは、Gitが変更のためにすべてのディレクトリをスキャンする必要があるため、予想よりも遅くなります。モジュールに多くのローカル履歴がある場合、クローンには永遠に時間がかかります。

プラス面としては、分散型バージョン管理システムは外部ソースとよりうまく統合できます。集中型モデルでは、外部プロジェクトの単一の任意のSNAPSHOTが独自のバージョン管理からエクスポートされ、その後、ベンダーブランチ上のローカルバージョン管理にインポートされます。すべての履歴が隠されます。分散型バージョン管理を使用すると、外部の履歴全体をクローンでき、開発をはるかに簡単に追跡し、ローカルの変更を再マージできます。

Gitのサブモジュールサポートにより、リポジトリは外部プロジェクトのチェックアウトをサブディレクトリとして含めることができます。サブモジュールは独自のIDを維持します。サブモジュールサポートは、サブモジュールリポジトリの場所とコミットIDのみを保存するため、含まれるプロジェクト(「スーパープロジェクト」)をクローンする他の開発者は、すべてのサブモジュールを同じリビジョンで簡単にクローンできます。スーパープロジェクトの部分的なチェックアウトも可能です。Gitにサブモジュールのどれも、いくつか、またはすべてをクローンするように指示できます。

git-submodule[1]コマンドはGit 1.5.3以降で利用可能です。Git 1.5.2のユーザーは、リポジトリ内のサブモジュールコミットを検索し、手動でチェックアウトできます。それ以前のバージョンではサブモジュールをまったく認識しません。

サブモジュールサポートがどのように機能するかを見るために、後でサブモジュールとして使用できる4つのサンプルリポジトリを作成します。

$ mkdir ~/git
$ cd ~/git
$ for i in a b c d
do
	mkdir $i
	cd $i
	git init
	echo "module $i" > $i.txt
	git add $i.txt
	git commit -m "Initial commit, submodule $i"
	cd ..
done

次にスーパープロジェクトを作成し、すべてのサブモジュールを追加します。

$ mkdir super
$ cd super
$ git init
$ for i in a b c d
do
	git submodule add ~/git/$i $i
done
スーパープロジェクトを公開する予定がある場合は、ここにローカルURLを使用しないでください!

git submoduleが作成したファイルを確認します。

$ ls -a
.  ..  .git  .gitmodules  a  b  c  d

git submodule add <repo> <path>コマンドはいくつかのことを行います。

  • <repo>からサブモジュールを現在のディレクトリ下の指定された<path>にクローンし、デフォルトでmasterブランチをチェックアウトします。

  • サブモジュールのクローンパスをgitmodules[5]ファイルに追加し、このファイルをインデックスに追加してコミット準備をします。

  • サブモジュールの現在のコミットIDをインデックスに追加し、コミット準備をします。

スーパープロジェクトをコミットします。

$ git commit -m "Add submodules a, b, c and d."

さて、スーパープロジェクトをクローンします。

$ cd ..
$ git clone super cloned
$ cd cloned

サブモジュールディレクトリはありますが、空です。

$ ls -a a
.  ..
$ git submodule status
-d266b9873ad50488163457f025db7cdd9683d88b a
-e81d457da15309b4fef4249aba9b50187999670d b
-c1536a972b9affea0f16e0680ba87332dc059146 c
-d96249ff5d57de5de093e6baff9e0aafa5276a74 d
上記に示されたコミットオブジェクト名はあなたにとっては異なるでしょうが、あなたのリポジトリのHEADコミットオブジェクト名と一致するはずです。git ls-remote ../aを実行して確認できます。

サブモジュールをプルダウンするプロセスは2段階です。まずgit submodule initを実行して、サブモジュールリポジトリのURLを.git/configに追加します。

$ git submodule init

次にgit submodule updateを使用して、リポジトリをクローンし、スーパープロジェクトで指定されたコミットをチェックアウトします。

$ git submodule update
$ cd a
$ ls -a
.  ..  .git  a.txt

git submodule updategit submodule addの主な違いは、git submodule updateがブランチの先端ではなく、特定のコミットをチェックアウトすることです。これはタグをチェックアウトするのと似ていて、ヘッドはデタッチされているため、ブランチ上で作業しているわけではありません。

$ git branch
* (detached from d266b98)
  master

サブモジュール内で変更を行う場合、デタッチされたHEADを使用している場合は、ブランチを作成またはチェックアウトし、変更を行い、サブモジュール内で変更を公開し、その後、スーパープロジェクトを更新して新しいコミットを参照する必要があります。

$ git switch master

または

$ git switch -c fix-up

次に、

$ echo "adding a line again" >> a.txt
$ git commit -a -m "Updated the submodule from within the superproject."
$ git push
$ cd ..
$ git diff
diff --git a/a b/a
index d266b98..261dfac 160000
--- a/a
+++ b/a
@@ -1 +1 @@
-Subproject commit d266b9873ad50488163457f025db7cdd9683d88b
+Subproject commit 261dfac35cb99d380eb966e102c1197139f7fa24
$ git add a
$ git commit -m "Updated submodule a."
$ git push

サブモジュールも更新したい場合は、git pullの後にgit submodule updateを実行する必要があります。

サブモジュールとの落とし穴

常に、サブモジュールを参照するスーパープロジェクトの変更を公開する前に、サブモジュールの変更を公開してください。サブモジュールの変更を公開し忘れると、他の人はリポジトリをクローンできなくなります。

$ cd ~/git/super/a
$ echo i added another line to this file >> a.txt
$ git commit -a -m "doing it wrong this time"
$ cd ..
$ git add a
$ git commit -m "Updated submodule a again."
$ git push
$ cd ~/git/cloned
$ git pull
$ git submodule update
error: pathspec '261dfac35cb99d380eb966e102c1197139f7fa24' did not match any file(s) known to git.
Did you forget to 'git add'?
Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a'

古いGitバージョンでは、サブモジュール内の新規または変更されたファイルをコミットし忘れることが容易にあり、それがサブモジュールの変更をプッシュしない場合と同様の問題を黙って引き起こしていました。Git 1.7.0以降、スーパープロジェクト内のgit statusgit diffの両方が、サブモジュールに新規または変更されたファイルが含まれている場合に、そのような状態の偶発的なコミットを防ぐためにサブモジュールが変更されたと表示します。git diffは、パッチ出力生成時や--submoduleオプション使用時に、作業ツリー側に-dirtyも追加します。

$ git diff
diff --git a/sub b/sub
--- a/sub
+++ b/sub
@@ -1 +1 @@
-Subproject commit 3f356705649b5d566d97ff843cf193359229a453
+Subproject commit 3f356705649b5d566d97ff843cf193359229a453-dirty
$ git diff --submodule
Submodule sub 3f35670..3f35670-dirty:

また、サブモジュール内のブランチを、いかなるスーパープロジェクトにも記録されたことのないコミットより巻き戻すべきではありません。

最初にブランチをチェックアウトせずに、サブモジュール内で変更を行いコミットした場合、git submodule updateを実行するのは安全ではありません。それらは黙って上書きされます。

$ cat a.txt
module a
$ echo line added from private2 >> a.txt
$ git commit -a -m "line added inside private2"
$ cd ..
$ git submodule update
Submodule path 'a': checked out 'd266b9873ad50488163457f025db7cdd9683d88b'
$ cd a
$ cat a.txt
module a
変更はサブモジュールのreflogで引き続き表示されます。

サブモジュールの作業ツリーに未コミットの変更がある場合、git submodule updateはそれらを上書きしません。代わりに、ダーティブランチから切り替えられないという通常の警告が表示されます。

低レベルGit操作

多くの高レベルコマンドは、元々、より小さなコアとなる低レベルGitコマンドを使用してシェルスクリプトとして実装されていました。これらは、Gitで珍しいことを行う場合や、その内部動作を理解する方法として、今でも役立つことがあります。

オブジェクトへのアクセスと操作

git-cat-file[1]コマンドは任意のオブジェクトの内容を表示できますが、通常はより高レベルのgit-show[1]の方が便利です。

git-commit-tree[1]コマンドは、任意の親とツリーを持つコミットを構築することを可能にします。

ツリーはgit-write-tree[1]で作成でき、そのデータはgit-ls-tree[1]でアクセスできます。2つのツリーはgit-diff-tree[1]で比較できます。

タグはgit-mktag[1]で作成され、署名はgit-verify-tag[1]で検証できますが、通常は両方にgit-tag[1]を使用する方が簡単です。

ワークフロー

git-commit[1]git-restore[1]のような高レベル操作は、作業ツリー、インデックス、オブジェクトデータベース間でデータを移動することで機能します。Gitは、これらの各ステップを個別に実行する低レベル操作を提供します。

一般的に、すべてのGit操作はインデックスファイルに対して機能します。一部の操作はインデックスファイルに対して**純粋に**機能します(インデックスの現在の状態を表示するなど)が、ほとんどの操作はインデックスファイルとデータベースまたは作業ディレクトリの間でデータを移動します。したがって、主に4つの組み合わせがあります。

作業ディレクトリ → インデックス

git-update-index[1]コマンドは、作業ディレクトリからの情報でインデックスを更新します。通常は、次のように更新したいファイル名を指定するだけでインデックス情報を更新します。

$ git update-index filename

しかし、ファイル名のグロビングなどによる一般的な間違いを避けるため、通常このコマンドはまったく新しいエントリを追加したり古いエントリを削除したりすることはありません。つまり、通常は既存のキャッシュエントリを更新するだけです。

Gitに、特定のファイルがもう存在しないこと、または新しいファイルを追加する必要があることを本当に理解していることを伝えるには、それぞれ--remove--addフラグを使用する必要があります。

注意!--removeフラグは、それに続くファイル名が必ずしも削除されることを意味する**わけではありません**。ファイルがまだディレクトリ構造に存在する場合、インデックスは新しいステータスで更新され、削除されません。--removeが意味する唯一のことは、update-indexが削除されたファイルを有効なものとみなすこと、そしてファイルが本当に存在しなくなった場合、それに応じてインデックスを更新することです。

特別なケースとして、git update-index --refreshを実行することもできます。これは、各インデックスの「stat」情報を現在のstat情報と一致するように更新します。オブジェクトの状態自体は更新**しません**し、オブジェクトが古いバッキングストアオブジェクトとまだ一致するかどうかをすばやくテストするために使用されるフィールドのみを更新します。

以前紹介したgit-add[1]は、git-update-index[1]のラッパーにすぎません。

インデックス → オブジェクトデータベース

現在のインデックスファイルをプログラムで「ツリー」オブジェクトに書き込みます。

$ git write-tree

これにはオプションは付属していません。現在のインデックスをその状態を記述するツリーオブジェクトのセットに書き込み、結果のトップレベルツリーの名前を返します。そのツリーを使用して、いつでも逆方向に移動することでインデックスを再生成できます。

オブジェクトデータベース → インデックス

オブジェクトデータベースから「ツリー」ファイルを読み込み、それを使用して現在のインデックスを埋めます(そして上書きします。後で復元したい未保存の状態がインデックスに含まれている場合はこれを行わないでください!)。通常の操作は次のとおりです。

$ git read-tree <SHA-1 of tree>

これで、インデックスファイルは以前に保存したツリーと同等になります。ただし、これは**インデックス**ファイルのみであり、作業ディレクトリの内容は変更されていません。

インデックス → 作業ディレクトリ

ファイルを「チェックアウト」することで、作業ディレクトリをインデックスから更新します。これはあまり一般的な操作ではありません。通常はファイルを更新したままにしておき、作業ディレクトリに書き込むのではなく、作業ディレクトリの変更についてインデックスファイルに伝えます(つまり、git update-index)。

しかし、新しいバージョンにジャンプしたり、他の人のバージョンをチェックアウトしたり、単に以前のツリーを復元したりすることを決定した場合、read-treeでインデックスファイルを埋め、その後、次で結果をチェックアウトする必要があります。

$ git checkout-index filename

または、インデックス全体をチェックアウトしたい場合は-aを使用します。

注意! git checkout-index は通常、古いファイルを上書きすることを拒否するため、既にツリーの古いバージョンをチェックアウトしている場合は、強制的にチェックアウトするために -f フラグ(-a フラグまたはファイル名の前に)を使用する必要があります。

最後に、ある表現から別の表現への純粋な移行ではない、いくつかの雑多な項目があります。

すべてをまとめる

git write-tree でインスタンス化したツリーをコミットするには、そのツリーとそれ以前の履歴(特に履歴で先行する「親」コミット)を参照する「コミット」オブジェクトを作成します。

通常、「コミット」には1つの親があります。特定の変更が行われる前のツリーの前の状態です。しかし、時には2つ以上の親コミットを持つことがあり、その場合、そのようなコミットが他のコミットによって表される2つ以上の前の状態を結合(「マージ」)するという事実から、それを「マージ」と呼びます。

言い換えれば、「ツリー」は作業ディレクトリの特定のディレクトリ状態を表すのに対し、「コミット」はその状態を時間的に表し、そこにどうやってたどり着いたかを説明します。

コミット時の状態を記述するツリーと、親のリストを与えてコミットオブジェクトを作成します。

$ git commit-tree <tree> -p <parent> [(-p <parent2>)...]

そして、標準入力 (パイプやファイルからのリダイレクト、または tty で直接入力) でコミット理由を与えます。

git commit-tree はそのコミットを表すオブジェクト名を返し、後で使用するために保存しておくべきです。通常、新しい HEAD 状態をコミットし、Git はその状態に関するメモをどこに保存しても気にしませんが、実際には、最後にコミットされた状態をいつでも確認できるように、結果を .git/HEAD が指すファイルに書き込む傾向があります。

以下は、さまざまな部品がどのように組み合わされているかを示す図です

                     commit-tree
                      commit obj
                       +----+
                       |    |
                       |    |
                       V    V
                    +-----------+
                    | Object DB |
                    |  Backing  |
                    |   Store   |
                    +-----------+
                       ^
           write-tree  |     |
             tree obj  |     |
                       |     |  read-tree
                       |     |  tree obj
                             V
                    +-----------+
                    |   Index   |
                    |  "cache"  |
                    +-----------+
         update-index  ^
             blob obj  |     |
                       |     |
    checkout-index -u  |     |  checkout-index
             stat      |     |  blob obj
                             V
                    +-----------+
                    |  Working  |
                    | Directory |
                    +-----------+

データの調査

オブジェクトデータベースとインデックスに表現されているデータは、さまざまなヘルパーツールで調べることができます。すべてのオブジェクトについて、git-cat-file[1] を使ってオブジェクトの詳細を調べることができます。

$ git cat-file -t <objectname>

オブジェクトの種類を示し、種類がわかれば (通常はオブジェクトを見つける場所から暗黙的にわかる)、

$ git cat-file blob|tree|commit|tag <objectname>

その内容を表示します。注意!ツリーはバイナリコンテンツを持っているので、そのコンテンツを表示するための特殊なヘルパー git ls-tree があり、バイナリコンテンツをより読みやすい形式に変換します。

「コミット」オブジェクトは小さく、かなり自己説明的な傾向があるため、特に参考になります。具体的には、.git/HEAD に一番上のコミット名を持つ慣習に従っていれば、次のことができます。

$ git cat-file commit HEAD

一番上のコミットが何であったかを確認するには。

複数のツリーをマージする

Gitはスリーウェイマージの実行を支援し、それはマージ手順を数回繰り返すことで多方向マージにも使用できます。通常は、1回のスリーウェイマージ(2つの履歴線を調整する)を行って結果をコミットしますが、必要であれば、複数のブランチを一度にマージできます。

スリーウェイマージを実行するには、マージしたい2つのコミットから開始し、それらの最も近い共通の親(3番目のコミット)を見つけ、これらの3つのコミットに対応するツリーを比較します。

マージの「ベース」を取得するには、2つのコミットの共通の親を検索します

$ git merge-base <commit1> <commit2>

これは、両方のコミットが基づいているコミットの名前を出力します。次に、これらのコミットのツリーオブジェクトを検索する必要がありますが、それは簡単に次の方法で実行できます。

$ git cat-file commit <commitname> | head -1

ツリーオブジェクト情報は常にコミットオブジェクトの最初の行に記載されているため。

マージする3つのツリー (「元の」ツリー、つまり共通のツリー、および2つの「結果」ツリー、つまりマージしたいブランチ) がわかったら、「マージ」読み込みをインデックスに実行します。これにより、古いインデックスの内容を破棄する必要がある場合に警告が表示されるため、それらをコミット済みであることを確認する必要があります。実際、通常は常に最後のコミットに対してマージを実行します (そのため、現在のインデックスにあるものと一致するはずです)。

マージを実行するには

$ git read-tree -m -u <origtree> <yourtree> <targettree>

これにより、すべての自明なマージ操作がインデックスファイルに直接実行され、git write-tree で結果を書き出すことができます。

複数ツリーのマージ、続き

残念ながら、多くのマージは自明ではありません。ファイルが追加、移動、削除された場合、または両方のブランチが同じファイルを変更した場合、インデックスツリーには「マージエントリ」が残ります。そのようなインデックスツリーはツリーオブジェクトに書き出すことはできません。結果を書き出す前に、他のツールを使用してそのようなマージの競合を解決する必要があります。

git ls-files --unmerged コマンドでこのようなインデックスの状態を調べることができます。例

$ git read-tree -m $orig HEAD $target
$ git ls-files --unmerged
100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1	hello.c
100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2	hello.c
100644 cc44c73eb783565da5831b4d820c962954019b69 3	hello.c

git ls-files --unmerged の各行は、blobモードビット、blob SHA-1、ステージ番号、ファイル名で始まります。 ステージ番号 は、そのツリーがどのツリーから来たかを示すGitの方法です。ステージ1は $orig ツリーに、ステージ2は HEAD ツリーに、ステージ3は $target ツリーに対応します。

以前、自明なマージは git read-tree -m の内部で行われると述べました。たとえば、ファイルが $orig から HEAD または $target に変更されなかった場合、またはファイルが $orig から HEAD および $orig から $target に同じ方法で変更された場合、明らかに最終結果は HEAD にあるものです。上記の例が示すのは、ファイル hello.c$orig から HEAD および $orig から $target に異なる方法で変更されたことです。これは、お気に入りの3方向マージプログラム(例:diff3merge、またはGit独自のmerge-file)を、これら3つのステージのblobオブジェクトに対して自分で実行することで解決できます。以下のように。

$ git cat-file blob 263414f >hello.c~1
$ git cat-file blob 06fa6a2 >hello.c~2
$ git cat-file blob cc44c73 >hello.c~3
$ git merge-file hello.c~2 hello.c~1 hello.c~3

これにより、マージ結果は hello.c~2 ファイルに残され、競合がある場合は競合マーカーも含まれます。マージ結果が理にかなっていることを確認したら、次の方法でこのファイルの最終マージ結果をGitに伝えることができます。

$ mv -f hello.c~2 hello.c
$ git update-index hello.c

パスが「未マージ」状態にある場合、そのパスに対して git update-index を実行すると、Gitにそのパスを解決済みとしてマークするように指示します。

上記はGitマージの最も低レベルでの説明であり、概念的に何が内部で行われているかを理解するのに役立ちます。実際には、Git自体でさえ、誰もこのために git cat-file を3回実行しません。git merge-index プログラムがあり、ステージを一時ファイルに抽出し、それに対して「マージ」スクリプトを呼び出します。

$ git merge-index git-merge-one-file hello.c

それが上位レベルの git merge -s resolve が実装されている方法です。

Gitのハッキング

この章では、Git実装の内部的な詳細について説明します。これはおそらくGit開発者のみが理解する必要があるでしょう。

オブジェクトの保存形式

すべてのオブジェクトは、オブジェクトの形式(つまり、どのように使用され、他のオブジェクトをどのように参照するか)を識別する、静的に決定された「型」を持っています。現在、4つの異なるオブジェクト型があります:「blob」、「tree」、「commit」、「tag」。

オブジェクトの種類に関わらず、すべてのオブジェクトは次の特徴を共有します。それらはすべてzlibで圧縮されており、種類だけでなくオブジェクト内のデータに関するサイズ情報も指定するヘッダーを持っています。オブジェクトの名前付けに使用されるSHA-1ハッシュは、元のデータとこのヘッダーのハッシュであることに注意する価値があります。したがって、sha1sum filefile のオブジェクト名と一致しません(Gitの初期バージョンはわずかに異なるハッシュ化を行っていましたが、結論は同じです)。

以下は、これらのハッシュを手動で生成する方法を示す短い例です。

いくつかの単純なコンテンツを持つ小さなテキストファイルを想定しましょう。

$ echo "Hello world" >hello.txt

このファイルにGitが使用するハッシュを手動で生成できるようになりました。

  • ハッシュを取得したいオブジェクトのタイプは「blob」で、サイズは12バイトです。

  • オブジェクトヘッダーをファイルコンテンツの前に付加し、これを sha1sum に渡します。

$ { printf "blob 12\0"; cat hello.txt; } | sha1sum
802992c4220de19a90767f3000a79a31b98d0df7  -

この手動で構築されたハッシュは、git hash-object を使用して検証できます。もちろん、これはヘッダーの追加を隠します。

$ git hash-object hello.txt
802992c4220de19a90767f3000a79a31b98d0df7

その結果、オブジェクトの一般的な整合性は、オブジェクトの内容や種類に関係なく常にテストできます。すべてのオブジェクトは、(a) ハッシュがファイルの内容と一致すること、および (b) オブジェクトが正常に展開されて <ascii-type-without-space> + <space> + <ascii-decimal-size> + <byte\0> + <binary-object-data> のシーケンスを形成するバイトストリームになることを検証することで検証できます。

構造化されたオブジェクトは、その構造と他のオブジェクトへの接続性をさらに検証することができます。これは通常、git fsck プログラムで行われます。このプログラムは、すべてのオブジェクトの完全な依存関係グラフを生成し、その内部整合性を検証します(ハッシュによる表面的な整合性の検証に加えて)。

Gitソースコードの鳥瞰図

新しい開発者がGitのソースコードを読み解くのは常に容易ではありません。このセクションでは、どこから始めればよいかについて少しヒントを提供します。

良い出発点は、最初のコミットの内容です。次のコマンドを使用します。

$ git switch --detach e83c5163

初期リビジョンは、今日のGitが持つほとんどすべてのものの基礎を築いています(詳細はいくつかの場所で異なるかもしれませんが)、しかし一度に読めるほど小さいです。

このリビジョン以降、用語が変更されていることに注意してください。たとえば、このリビジョンのREADMEでは、現在コミットと呼んでいるものを説明するために「changeset」という言葉を使用しています。

また、現在は「キャッシュ」とは呼ばず、「インデックス」と呼びますが、ファイル名は依然として read-cache.h です。

最初のコミットの概念を理解したら、より新しいバージョンをチェックアウトし、read-cache-ll.hobject.hcommit.h をざっと読んでみてください。

初期の頃のGit(UNIXの伝統に倣い)は、非常にシンプルなプログラム群であり、スクリプトで、あるプログラムの出力を別のプログラムにパイプして使用していました。これは新しいことをテストするのが簡単だったため、初期の開発には良かったことが判明しました。しかし、最近ではこれらの部品の多くが組み込みになり、コアの一部はパフォーマンス、移植性、コードの重複を避けるために「ライブラリ化」、つまりlibgit.aに組み込まれました。

これで、インデックスが何であるか (read-cache-ll.h に対応するデータ構造があります) と、ほんのいくつかのオブジェクトタイプ (ブロブ、ツリー、コミット、タグ) があり、それらが struct object から共通の構造を継承していること (そして、例えば (struct object *)commit をキャストすることで &commit->object同じことを達成できること、つまりオブジェクト名とフラグにアクセスできること) を知っているはずです。

ここで一息ついて、この情報を消化するのに良いタイミングです。

次のステップ:オブジェクトの命名に慣れること。コミットの命名を読んでください。オブジェクト(およびリビジョンだけでなく!)を命名する方法はかなり多くあります。これらすべては sha1_name.c で処理されます。関数 get_sha1() をざっと見てください。get_sha1_basic() のような関数によって多くの特殊な処理が行われます。

これは、Gitの最もライブラリ化された部分、リビジョンウォーカーに入るための準備です。

基本的に、git log の初期バージョンはシェルスクリプトでした。

$ git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | \
	LESS=-S ${PAGER:-less}

これは何を意味するのでしょうか?

git rev-list はリビジョンウォーカーのオリジナルバージョンで、常にリビジョンのリストを標準出力に表示していました。ほとんどの新しいGitコマンドは git rev-list を使用するスクリプトとして始まるため、現在でも機能しており、その必要があります。

git rev-parse はもはやそれほど重要ではありません。スクリプトによって呼び出されるさまざまなプラミングコマンドに関連するオプションをフィルタリングするためにのみ使用されていました。

git rev-list が行ったことのほとんどは revision.crevision.h に含まれています。これはオプションを rev_info という構造体にラップし、リビジョンがどのように、どのリビジョンをたどるかを制御します。

git rev-parse の本来の仕事は、現在 setup_revisions() 関数によって行われています。これは、リビジョンウォーカーの共通のコマンドラインオプションを解析します。この情報は、後で使用するために rev_info 構造体に格納されます。setup_revisions() を呼び出した後に、独自のコマンドラインオプションの解析を行うことができます。その後、初期化のために prepare_revision_walk() を呼び出し、次に get_revision() 関数でコミットを1つずつ取得できます。

リビジョンウォーク処理の詳細に興味がある場合は、cmd_log() の最初の実装を参照してください。git show v1.3.0~155^2~4 を呼び出し、その関数までスクロールしてください(setup_pager() を直接呼び出す必要はなくなりました)。

現在、git log は組み込みコマンドであり、git コマンドに含まれています。組み込みコマンドのソース側は次のとおりです。

  • cmd_<bla> という関数で、通常は builtin/<bla.c> で定義され (古いバージョンのGitでは builtin-<bla>.c にあったことに注意)、builtin.h で宣言されています。

  • git.ccommands[] 配列のエントリ、および

  • MakefileBUILTIN_OBJECTS のエントリ。

時には、複数の組み込みコマンドが1つのソースファイルに含まれることがあります。例えば、cmd_whatchanged()cmd_log() はどちらも builtin/log.c にありますが、これはかなりのコードを共有しているためです。この場合、それらが存在する .c ファイルと同じ名前でないコマンドは、MakefileBUILT_INS にリストアップする必要があります。

git log はC言語では元のスクリプトよりも複雑に見えますが、これによりはるかに高い柔軟性とパフォーマンスが得られます。

ここでも、一旦休憩するのに良いタイミングです。

レッスン3:コードを勉強する。本当に、Gitの組織について学ぶための最良の方法です(基本的な概念を知った後であれば)。

では、「オブジェクト名だけを知っていて、blobにアクセスするにはどうすればよいか?」というような、興味のあることを考えてみてください。最初のステップは、それを行うことができるGitコマンドを見つけることです。この例では、git show または git cat-file のいずれかです。

分かりやすさのために git cat-file に留まりましょう。なぜなら、

  • はプラミングであり、

  • 最初のコミットから存在していました (文字通り cat-file.c として約20リビジョンしか経ておらず、組み込みコマンドになった際に builtin/cat-file.c に改名され、その後10バージョン未満しか変更されていません)。

そこで、builtin/cat-file.c を見て、cmd_cat_file() を検索し、それが何をしているかを確認してください。

        git_config(git_default_config);
        if (argc != 3)
		usage("git cat-file [-t|-s|-e|-p|<type>] <sha1>");
        if (get_sha1(argv[2], sha1))
                die("Not a valid object name %s", argv[2]);

明らかな詳細は飛ばしましょう。ここで本当に興味深い唯一の部分は、get_sha1() の呼び出しです。これは argv[2] をオブジェクト名として解釈しようとし、それが現在のリポジトリに存在するオブジェクトを参照している場合、結果のSHA-1を変数 sha1 に書き込みます。

ここで興味深い点が2つあります。

  • get_sha1()成功時に0を返します。これは新しいGitハッカーを驚かせるかもしれませんが、UNIXでは異なるエラーの場合に異なる負の数を返し、成功時には0を返すという長い伝統があります。

  • get_sha1() 関数のシグネチャにおける変数 sha1unsigned char * ですが、実際には unsigned char[20] へのポインタであると想定されています。この変数には、指定されたコミットの160ビットSHA-1が含まれます。SHA-1が unsigned char * として渡される場合、それはバイナリ表現であり、16進文字のASCII表現とは対照的に、char * として渡されることに注意してください。

これらの両方がコード全体に見られるでしょう。

さて、本題です。

        case 0:
                buf = read_object_with_reference(sha1, argv[1], &size, NULL);

これがblob (実際にはblobだけでなく、あらゆる種類のオブジェクト) の読み込み方法です。関数 read_object_with_reference() が実際にどのように機能するかを知るには、そのソースコードを探し (Gitリポジトリ内で git grep read_object_with | grep ":[a-z]" のようなもの)、ソースを読んでください。

結果をどのように使用できるかを知るには、cmd_cat_file() を読み進めてください。

        write_or_die(1, buf, size);

機能の場所が分からないことがあります。多くの場合、git log の出力から検索し、その後、対応するコミットを git show で表示すると役立ちます。

例:git bundle のテストケースがあったことは知っているが、それがどこにあったか覚えていない場合(はい、git grep bundle t/することもできますが、それではポイントが伝わりません!)

$ git log --no-merges t/

ページャー(less)で「bundle」を検索し、数行戻って、それがコミット18449ab0にあることを確認します。このオブジェクト名をコピーして、コマンドラインに貼り付けるだけです。

$ git show 18449ab0

ほら。

別の例:スクリプトを組み込みコマンドにするにはどうすればよいかを見つける。

$ git log --no-merges --diff-filter=A builtin/*.c

ご覧のとおり、Git自体に関するソースコードを見つけるのに、Gitは実は最高のツールなのです!

Git用語集

Gitの解説

代替オブジェクトデータベース

alternatesメカニズムを通じて、リポジトリは、そのオブジェクトデータベースの一部を「代替」と呼ばれる別のオブジェクトデータベースから継承できます。

ベアリポジトリ

ベアリポジトリとは、通常、適切な名前の ディレクトリ で、.git のサフィックスを持ち、バージョン管理下のファイルのローカルにチェックアウトされたコピーを持たないものです。つまり、通常は隠しディレクトリ .git にあるGitの管理ファイルと制御ファイルはすべて、代わりに repository.git ディレクトリに直接存在し、他のファイルは存在せずチェックアウトもされていません。通常、公開リポジトリの提供者はベアリポジトリを利用可能にします。

blob オブジェクト

型のないオブジェクト、例えばファイルの内容など。

ブランチ

「ブランチ」とは、開発のラインのことです。ブランチ上の最新のコミットは、そのブランチの先端と呼ばれます。ブランチの先端は、ブランチヘッドによって参照され、ブランチ上で追加開発が行われるにつれて前方に移動します。単一のGitリポジトリは任意の数のブランチを追跡できますが、作業ツリーはそれらのうちの1つ(「現在の」または「チェックアウトされた」ブランチ)のみに関連付けられており、HEADはそのブランチを指しています。

キャッシュ

廃止: index

チェイン

オブジェクトのリストで、リスト内の各オブジェクトがその次のオブジェクトへの参照を含んでいる(例えば、コミットの次のオブジェクトはそのの1つである可能性がある)。

チェンジセット

BitKeeper/cvsps でいうところの「コミット」。Git は変更ではなく状態を保存するため、「チェンジセット」という用語を Git で使用することはあまり意味がありません。

チェックアウト

オブジェクトデータベースからツリーオブジェクトまたはblobを使用して作業ツリーの全部または一部を更新し、作業ツリー全体が新しいブランチを指すようにされた場合はインデックスHEADを更新する操作。

チェリーピック

SCM 用語で「チェリーピック」とは、一連の変更(通常はコミット)から一部の変更を選択し、それを別のコードベースの上に新しい一連の変更として記録することを意味します。Gitでは、既存のコミットによって導入された変更を抽出し、現在のブランチの先端に基づいて新しいコミットとして記録するために、「git cherry-pick」コマンドが実行されます。

クリーン

作業ツリーが、現在のヘッドが参照するリビジョンに対応している場合、クリーンであると言われます。また、「ダーティ」も参照してください。

コミット

名詞として:Git履歴における単一の時点。プロジェクト全体の履歴は、相互に関連するコミットの集合として表現されます。「コミット」という言葉は、他のバージョン管理システムが「リビジョン」や「バージョン」という言葉を使用する場所で、Gitによってしばしば使用されます。コミットオブジェクトの略語としても使用されます。

動詞として: インデックスの現在の状態を表す新しいコミットを作成し、HEAD を新しいコミットを指すように進めることで、プロジェクトの状態の新しいスナップショットを Git 履歴に保存する操作。

コミットグラフの概念、表現、および使用法

オブジェクトデータベース内のコミットによって形成され、ブランチの先端によって参照され、それらのリンクされたコミットのチェーンを使用するDAG構造の同義語です。この構造は決定的なコミットグラフです。グラフは他の方法で表現することもできます。例えば、「コミットグラフ」ファイルなどです。

コミットグラフファイル

「コミットグラフ」ファイル(通常はハイフンで区切られる)は、コミットグラフを補完する表現であり、コミットグラフの走査を高速化します。「コミットグラフ」ファイルは、.git/objects/info ディレクトリまたは代替オブジェクトデータベースのinfoディレクトリに保存されます。

コミットオブジェクト

オブジェクトで、、コミッター、作者、日付、そして保存されたリビジョンのトップディレクトリに対応するツリーオブジェクトなどの、特定のリビジョンに関する情報を含みます。

コミットイッシュ (committish とも呼ばれる)

コミットオブジェクト、または再帰的にデリファレンスしてコミットオブジェクトにできるオブジェクト。以下はすべてコミットイッシュです:コミットオブジェクト、コミットオブジェクトを指すタグオブジェクト、コミットオブジェクトを指すタグオブジェクトを指すタグオブジェクトなど。

コアGit

Gitの基本的なデータ構造とユーティリティ。限られたソースコード管理ツールのみを公開します。

DAG

有向非巡回グラフ。コミットオブジェクトは有向非巡回グラフを形成します。なぜなら、それらは親を持ち(有向)、コミットオブジェクトのグラフは非巡回であるためです(同じオブジェクトで始まり終わるチェーンはありません)。

ダングリングオブジェクト

到達不能なオブジェクトで、他の到達不能なオブジェクトからも到達できません。ダングリングオブジェクトは、リポジトリ内のどの参照やオブジェクトからも参照されていません。

デリファレンス

シンボリックリファレンスの参照:シンボリックリファレンスが指すリファレンスにアクセスする操作。再帰的デリファレンスは、非シンボリックリファレンスが見つかるまで、結果のリファレンスに対して前述のプロセスを繰り返すことを含みます。

タグオブジェクトの参照:タグが指すオブジェクトにアクセスする操作。タグは、結果が指定されたオブジェクトタイプ(該当する場合)または非「タグ」オブジェクトタイプになるまで、結果オブジェクトに対して操作を繰り返すことで再帰的にデリファレンスされます。タグの文脈における「再帰的デリファレンス」の同義語は「ピール」です。

コミットオブジェクトの参照:コミットのツリーオブジェクトにアクセスする操作。コミットは再帰的にデリファレンスすることはできません。

特に指定がない限り、Git コマンドまたはプロトコルの文脈で使用される「デリファレンス」は暗黙的に再帰的です。

デタッチド HEAD

通常、HEADブランチ名を格納し、HEADが表す履歴を操作するコマンドは、HEADが指すブランチの先端に至る履歴を操作します。しかし、Gitでは、特定のブランチの先端であるとは限らない任意のコミットチェックアウトすることもできます。このような状態のHEADは「デタッチド」と呼ばれます。

現在のブランチの履歴を操作するコマンド(例:git commit でその上に新しい履歴を構築する)は、HEADがデタッチされていても機能することに注意してください。それらは、HEADを更新された履歴の先端を指すように更新し、どのブランチにも影響を与えません。現在のブランチに関する情報を更新または問い合わせるコマンド(例:git branch --set-upstream-to で現在のブランチが統合するリモートトラッキングブランチを設定する)は、この状態では(実際の)現在のブランチが存在しないため、明らかに機能しません。

ディレクトリ

"ls" で取得できるリスト :-) です。

ダーティ

作業ツリーが、現在のブランチコミットされていない変更を含んでいる場合、「ダーティ」であると言われます。

イービルマージ

邪悪なマージとは、どのにも存在しない変更を導入するマージのことです。

ファストフォワード

ファストフォワードとは、特別な種類のマージであり、既存のリビジョンに対して、その子孫である別のブランチの変更を「マージ」するものです。このような場合、新しいマージコミットを作成するのではなく、現在のブランチを、マージしようとしているブランチと同じリビジョンを指すように更新するだけです。これは、リモートリポジトリリモートトラッキングブランチで頻繁に発生します。

フェッチ

ブランチをフェッチするとは、リモートリポジトリからそのブランチのhead refを取得し、ローカルのオブジェクトデータベースに不足しているオブジェクトを特定して、それらも取得することです。git-fetch[1] も参照してください。

ファイルシステム

リーナス・トーバルズは元々Gitをユーザー空間ファイルシステム、つまりファイルやディレクトリを保持するためのインフラストラクチャとして設計しました。これにより、Gitの効率と速度が確保されました。

Gitアーカイブ

リポジトリの同義語(arch関係者向け)。

gitfile

作業ツリーのルートにある .git というプレーンなファイルで、実際のレポジトリであるディレクトリを指します。適切な使用方法については、git-worktree[1] または git-submodule[1] を参照してください。構文については、gitrepository-layout[5] を参照してください。

グラフト

グラフトとは、コミットに偽の祖先情報を記録することで、それ以外は異なる2つの開発ラインを結合できるようにするものです。これにより、Gitは、コミットが作成されたときに記録されたものとは異なるのセットを持つと偽装させることができます。.git/info/grafts ファイルで設定されます。

グラフトメカニズムは古く、リポジトリ間のオブジェクト転送で問題を引き起こす可能性があることに注意してください。同じことをより柔軟で堅牢なシステムで行うには、git-replace[1] を参照してください。

ハッシュ

Gitの文脈では、オブジェクト名の同義語。

ヘッド

ブランチの先端にあるコミットを指す名前付き参照。ヘッドは、パックされた参照を使用する場合を除き、$GIT_DIR/refs/heads/ ディレクトリ内のファイルに格納されます。git-pack-refs[1] を参照してください。

HEAD

現在のブランチ。より詳しく言えば、あなたの作業ツリーは通常、HEADが参照するツリーの状態から派生しています。HEADはリポジトリ内のいずれかのヘッドへの参照ですが、デタッチされたHEADを使用している場合は、任意のコミットを直接参照します。

ヘッド参照

head の同義語。

フック

いくつかのGitコマンドの通常の実行中に、開発者が機能を追加したりチェックしたりできるオプションのスクリプトへの呼び出しが行われます。通常、フックはコマンドが事前検証され、場合によっては中止されることを可能にし、操作完了後の事後通知を可能にします。フックスクリプトは $GIT_DIR/hooks/ ディレクトリにあり、ファイル名から .sample サフィックスを削除するだけで有効になります。Gitの以前のバージョンでは、それらを実行可能にする必要がありました。

インデックス

stat情報を持つファイルのコレクションで、その内容はオブジェクトとして格納されます。インデックスは、あなたの作業ツリーの保存されたバージョンです。実際には、マージ時に使用される、作業ツリーの2番目、さらには3番目のバージョンを含むこともできます。

インデックスエントリ

インデックスに格納されている特定のファイルに関する情報。マージが開始され、まだ完了していない場合(つまり、インデックスにそのファイルの複数のバージョンが含まれている場合)、インデックスエントリは未マージ状態になることがあります。

マスター

デフォルトの開発ブランチ。Gitリポジトリを作成すると、常に「master」という名前のブランチが作成され、アクティブなブランチになります。ほとんどの場合、これにはローカル開発が含まれますが、それは純粋に慣習によるものであり、必須ではありません。

マージ

動詞として: 他のブランチ (場合によっては外部リポジトリからのもの) の内容を現在のブランチに取り込むこと。マージされるブランチが異なるリポジトリからのものである場合、これはまずリモートブランチをフェッチし、その結果を現在のブランチにマージすることで行われます。このフェッチとマージの操作の組み合わせはプルと呼ばれます。マージは、ブランチが分岐してからの変更を識別し、それらの変更をすべてまとめて適用する自動プロセスによって実行されます。変更が競合する場合、マージを完了するために手動での介入が必要になることがあります。

名詞として: ファストフォワードでない限り、成功したマージは、マージの結果を表す新しいコミットの作成と、マージされたブランチの先端をとするコミットの作成につながります。このコミットは「マージコミット」または単に「マージ」と呼ばれます。

オブジェクト

Gitにおけるストレージの単位。その内容のSHA-1によって一意に識別されます。したがって、オブジェクトは変更できません。

オブジェクトデータベース

「オブジェクト」のセットを格納し、個々のオブジェクトオブジェクト名によって識別されます。オブジェクトは通常 $GIT_DIR/objects/ に存在します。

オブジェクト識別子 (oid)

オブジェクト名の同義語。

オブジェクト名

オブジェクトの一意の識別子。オブジェクト名は通常、40文字の16進数文字列で表現されます。口語的にはSHA-1とも呼ばれます。

オブジェクトタイプ

オブジェクトのタイプを記述する識別子で、"commit"、"tree"、"tag"、"blob" のいずれか。

オクトパス

2つ以上のブランチマージすること。

孤立

まだ存在しない(つまり、生まれたばかりの)ブランチに乗る行為。このような操作の後、最初に作成されたコミットは親を持たないコミットとなり、新しい履歴を開始します。

オリジン

デフォルトのアップストリームリポジトリ。ほとんどのプロジェクトには、追跡しているアップストリームプロジェクトが少なくとも1つあります。デフォルトでは、その目的のためにoriginが使用されます。新しいアップストリームの更新は、git branch -r を使用して確認できる、origin/name-of-upstream-branchという名前のリモートトラッキングブランチにフェッチされます。

オーバーレイ

ワーキングディレクトリ内のファイルのみを更新および追加し、削除しないモード。これは cp -R が宛先ディレクトリ内のコンテンツを更新する方法に似ています。インデックスまたはtree-ishからファイルをチェックアウトする際のチェックアウトのデフォルトモードです。対照的に、no-overlayモードは、ソースに存在しない追跡ファイルも削除します。これは rsync --delete に似ています。

パック

1つのファイルに圧縮されたオブジェクトのセット (スペースを節約したり、効率的に送信したりするため)。

パックインデックス

パック内のオブジェクトのコンテンツに効率的にアクセスできるようにするための、パック内のオブジェクトの識別子とその他の情報のリスト。

パススペック

Gitコマンドでパスを制限するために使用されるパターン。

パススペックは、「git ls-files」、「git ls-tree」、「git add」、「git grep」、「git diff」、「git checkout」など、多くのコマンドのコマンドラインで使用され、操作の範囲をツリーまたは作業ツリーの一部に限定します。パスが現在のディレクトリに対して相対的か、トップレベルに対して相対的かについては、各コマンドのドキュメントを参照してください。パススペックの構文は次のとおりです。

  • 任意のパスはそれ自体に一致します。

  • 最後のスラッシュまでのパススペックは、ディレクトリプレフィックスを表します。そのパススペックのスコープは、そのサブツリーに限定されます。

  • 残りのパススペックは、パス名の残りの部分に対するパターンです。ディレクトリプレフィックスに対する相対パスは、fnmatch(3) を使用してそのパターンと照合されます。特に、*? はディレクトリセパレータと一致する可能性があります

例えば、Documentation/*.jpg は Documentation サブツリー内のすべての .jpg ファイルに一致し、Documentation/chapter_1/figure_1.jpg も含まれます。

コロン : で始まるパススペックは特別な意味を持ちます。短い形式では、先頭のコロン : の後に0個以上の「マジックシグネチャ」文字が続き(オプションで別のコロン : で終了します)、残りはパスと一致させるパターンです。「マジックシグネチャ」は、英数字、グロブ、正規表現の特殊文字、コロンのいずれでもないASCII記号で構成されます。パターンが「マジックシグネチャ」記号セットに属さない文字またはコロン以外の文字で始まる場合、マジックシグネチャを終了するオプションのコロンを省略できます。

長い形式では、先頭のコロン : の後に開き括弧 (、0個以上の「マジックワード」をカンマ区切りで並べたリスト、閉じ括弧 ) が続き、残りはパスとマッチするパターンとなります。

コロンのみのパススペックは「パススペックなし」を意味します。この形式は他のパススペックと組み合わせるべきではありません。

トップ

マジックワード top (マジックシグネチャ: /) は、サブディレクトリ内でコマンドを実行している場合でも、パターンを作業ツリーのルートからマッチさせます。

リテラル

パターン内のワイルドカード(*? など)は、リテラル文字として扱われます。

icase

大文字・小文字を区別しないマッチング。

グロブ

Gitはパターンをfnmatch(3)がFNM_PATHNAMEフラグ付きで使用するのに適したシェルグロブとして扱います。パターン内のワイルドカードはパス名内の/とは一致しません。例えば、「Documentation/*.html」は「Documentation/git.html」とは一致しますが、「Documentation/ppc/ppc.html」や「tools/perf/Documentation/perf.html」とは一致しません。

完全パス名と照合されるパターン内の2つの連続したアスタリスク(「**」)には特別な意味がある場合があります。

  • スラッシュに続く先頭の「**」は、すべてのディレクトリ内で一致することを意味します。例えば、「**/foo」は、どこにあるファイルまたはディレクトリ「foo」にも一致し、パターン「foo」と同じです。「**/foo/bar」は、ディレクトリ「foo」の直下にある任意の場所のファイルまたはディレクトリ「bar」に一致します。

  • 末尾の「/**」は、その内部にあるすべてに一致します。たとえば、「abc/**」は、.gitignore ファイルの場所を基準として、深さ無制限でディレクトリ「abc」内のすべてのファイルに一致します。

  • スラッシュ、2つの連続するアスタリスク、スラッシュと続くパターンは、ゼロ個以上のディレクトリに一致します。たとえば、「a/**/b」は「a/b」、「a/x/b」、「a/x/y/b」などに一致します。

  • その他の連続するアスタリスクは無効とみなされます。

    グロブマジックはリテラルマジックと互換性がありません。

属性

attr: の後には、スペース区切りの「属性要件」のリストが続きます。パスが一致と見なされるためには、これらの要件をすべて満たす必要があります。これは通常の非マジックパススペックパターンマッチングに追加されます。gitattributes[5] を参照してください。

パスの属性要件は、次のいずれかの形式を取ります。

  • ATTR」は、属性 ATTR が設定されていることを要求します。

  • -ATTR」は、属性 ATTR が設定されていないことを要求します。

  • ATTR=VALUE」は、属性 ATTR が文字列 VALUE に設定されていることを要求します。

  • !ATTR」は、属性 ATTR が未指定であることを要求します。

    ツリーオブジェクトと照合する場合でも、属性は与えられたツリーオブジェクトからではなく、作業ツリーから取得されることに注意してください。

除外

パスが除外パススペック以外のいずれかのパススペックに一致した後、すべての除外パススペック(マジックシグネチャ: ! またはその同義語 ^)を通過します。一致した場合、そのパスは無視されます。除外パススペック以外のパススペックがない場合、除外はパススペックなしで呼び出されたかのように結果セットに適用されます。

コミットオブジェクトは、開発ラインにおける論理的な先行(つまりその親)の(空である可能性のある)リストを含んでいます。

剥がす (peel)

タグオブジェクトを再帰的にデリファレンスする操作。

ピッケル

ピッケルという用語は、特定のテキスト文字列を追加または削除する変更を選択するのに役立つdiffcoreルーチンへのオプションを指します。--pickaxe-all オプションを使用すると、例えば特定のテキスト行を導入または削除した完全なチェンジセットを表示することができます。git-diff[1] を参照してください。

プラミング

コアGitのかわいい名前。

ポーセリン

コアGitに依存するプログラムやプログラムスイートのかわいい名前で、コアGitへの高レベルなアクセスを提供します。ポーセリンは、プラミングよりもSCMインターフェースをより多く露出させます。

ワークツリーごとの参照

グローバルではなく、ワークツリーごとの参照。現在はHEADおよびrefs/bisect/で始まる参照のみですが、将来的には他の特殊な参照も含まれる可能性があります。

擬似参照

通常の参照とは異なるセマンティクスを持つ参照。これらの参照は通常のGitコマンドで読み取ることができますが、git-update-ref[1]のようなコマンドで書き込むことはできません。

以下の擬似参照がGitに認識されています。

  • FETCH_HEADgit-fetch[1] または git-pull[1] によって書き込まれます。複数のオブジェクトIDを参照する場合があります。各オブジェクトIDには、どこからフェッチされたか、およびそのフェッチステータスを示すメタデータが注釈付けされています。

  • MERGE_HEAD は、マージの競合を解決する際に git-merge[1] によって書き込まれます。マージされるすべてのコミットIDを含んでいます。

プル

ブランチをプルするとは、それをフェッチしてマージすることです。git-pull[1] も参照してください。

プッシュ

ブランチをプッシュするとは、リモートリポジトリからそのブランチのhead refを取得し、それがブランチのローカルhead refの祖先であるかどうかを調べ、その場合、ローカルhead refから到達可能で、リモートリポジトリに存在しないすべてのオブジェクトをリモートオブジェクトデータベースに入れ、リモートhead refを更新することです。リモートheadがローカルheadの祖先でない場合、プッシュは失敗します。

到達可能

特定のコミットのすべての祖先は、そのコミットから「到達可能」であると言われます。より一般的には、あるオブジェクトが別のオブジェクトから到達可能であるとは、タグが指すもの、コミットがその親またはツリー、ツリーがそれらが含むツリーまたはblobをたどるチェーンによって、一方から他方に到達できる場合を指します。

到達可能性ビットマップ

到達可能性ビットマップは、パックファイル内、またはマルチパックインデックス(MIDX)内にある選択されたコミットセットの到達可能性に関する情報を格納し、オブジェクト検索を高速化します。ビットマップは「.bitmap」ファイルに格納されます。リポジトリは最大で1つのビットマップファイルを使用できます。ビットマップファイルは、1つのパック、またはリポジトリのマルチパックインデックス(存在する場合)のいずれかに属することができます。

リベース

ブランチからの変更の系列を別のベースに再適用し、そのブランチのヘッドを結果にリセットすること。

参照 (ref)

オブジェクト名または別の参照(後者はシンボリック参照と呼ばれる)を指す名前。便宜上、Gitコマンドの引数として使用される場合、参照は省略されることがあります。詳細については、gitrevisions[7] を参照してください。参照はリポジトリに格納されます。

ref名前空間は階層的です。ref名は refs/ で始まるか、階層のルートに配置されている必要があります。後者の場合、その名前は次のルールに従う必要があります。

  • 名前はすべて大文字またはアンダースコアのみで構成されます。

  • 名前は「_HEAD」で終わるか、「HEAD」と等しいこと。

    階層のルートには、これらのルールに一致しない不規則な参照がいくつかあります。以下のリストは網羅的であり、将来的には拡張されることはありません。

  • AUTO_MERGE

  • BISECT_EXPECTED_REV

  • NOTES_MERGE_PARTIAL

  • NOTES_MERGE_REF

  • MERGE_AUTOSTASH

    異なるサブ階層は異なる目的で使用されます。例えば、refs/heads/ 階層はローカルブランチを表すために使用され、refs/tags/ 階層はローカルタグを表すために使用されます。

参照ログ (reflog)

参照ログは、参照のローカルな「履歴」を示します。言い換えれば、このリポジトリで3つ前のリビジョンが何であったか、そして昨日午後9時14分にこのリポジトリの現在の状態がどうであったかを教えてくれます。git-reflog[1] で詳細を参照してください。

参照指定子 (refspec)

「refspec」は、リモートrefとローカルrefのマッピングを記述するために、フェッチプッシュによって使用されます。詳細については、git-fetch[1]またはgit-push[1]を参照してください。

リモートリポジトリ

同じプロジェクトを追跡するために使用されるが、別の場所に存在するリポジトリ。リモートと通信するには、フェッチまたはプッシュを参照してください。

リモートトラッキングブランチ

別のリポジトリからの変更を追跡するために使用される参照。通常、refs/remotes/foo/bar のような形をしており(これはリモートfoobarという名前のブランチを追跡していることを示します)、設定されたフェッチrefspecの右辺に一致します。リモートトラッキングブランチには直接的な変更を加えたり、ローカルコミットを作成したりすべきではありません。

リポジトリ

参照の集合と、参照から到達可能なすべてのオブジェクトを含むオブジェクトデータベースであり、場合によっては1つ以上のポーセリンからのメタデータが付随する。リポジトリは、代替メカニズムを介して他のリポジトリとオブジェクトデータベースを共有できる。

解決

自動マージが失敗して残されたものを手動で修正する操作。

リビジョン

コミット(名詞)の同義語。

巻き戻し

開発の一部を破棄すること、つまりヘッドを以前のリビジョンに割り当てること。

SCM

ソースコード管理(ツール)。

SHA-1

「Secure Hash Algorithm 1」; 暗号学的ハッシュ関数。Git の文脈では、オブジェクト名の同義語として使用されます。

シャロークローン

ほとんどシャローリポジトリの同義語ですが、この表現は git clone --depth=... コマンドを実行して作成されたことをより明確に示しています。

シャローリポジトリ

シャローリポジトリは、不完全な履歴を持ち、その一部のコミットが削除されています(言い換えれば、Gitは、これらのコミットがコミットオブジェクトに記録されているにもかかわらず、親を持たないかのように振る舞います)。これは、アップストリームで記録されている実際の履歴がはるかに大きい場合でも、プロジェクトの最近の履歴のみに興味がある場合に役立つことがあります。シャローリポジトリは、git-clone[1]--depth オプションを与えることで作成され、その履歴は後で git-fetch[1] で深めることができます。

スタッシュエントリ

将来の再利用のために、ダーティな作業ディレクトリとインデックスの内容を一時的に保存するために使用されるオブジェクト

サブモジュール

別のリポジトリ(後者はスーパープロジェクトと呼ばれる)の中に、別のプロジェクトの履歴を保持するリポジトリ

スーパープロジェクト

その作業ツリー内に、他のプロジェクトのリポジトリをサブモジュールとして参照するリポジトリ。スーパープロジェクトは、含まれるサブモジュールのコミットオブジェクトの名前を知っています(ただし、コピーは保持しません)。

シンボリック参照 (symref)

シンボリック参照:SHA-1 ID自体を含まず、ref: refs/some/thing という形式であり、参照された場合、この参照に再帰的にデリファレンスします。HEAD はシンボリック参照の代表例です。シンボリック参照は git-symbolic-ref[1] コマンドで操作されます。

タグ

refs/tags/ 名前空間の下にある参照で、任意のタイプのオブジェクトを指します(通常、タグはタグまたはコミットオブジェクトのいずれかを指します)。ヘッドとは異なり、タグはcommitコマンドによって更新されません。Gitのタグは、Lispのタグ(Gitの文脈ではオブジェクトタイプと呼ばれる)とは何の関係もありません。タグは通常、コミット祖先チェーン内の特定の時点をマークするために使用されます。

タグオブジェクト

別のオブジェクトを指す参照を含むオブジェクトで、コミットオブジェクトと同様にメッセージを含むことができます。また、(PGP)署名を含むことができ、その場合は「署名付きタグオブジェクト」と呼ばれます。

トピックブランチ

開発者が概念的な開発ラインを識別するために使用する通常のGitブランチ。ブランチは非常に簡単で安価なので、それぞれが非常によく定義された概念や小さな増分的な関連する変更を含む複数の小さなブランチを持つことが望ましい場合があります。

トレーラー

キーと値のメタデータ。トレーラーはコミットメッセージの最後にオプションで配置されます。他のコミュニティでは「フッター」や「タグ」と呼ばれることもあります。git-interpret-trailers[1] を参照してください。

ツリー

作業ツリー、または依存するblobとツリーオブジェクトを含むツリーオブジェクト(つまり、作業ツリーの保存された表現)のいずれか。

ツリーオブジェクト

ファイル名とモードのリスト、および関連するblobオブジェクトやツリーオブジェクトへの参照を含むオブジェクトツリーディレクトリと同等です。

ツリーイッシュ (treeish とも呼ばれる)

ツリーオブジェクト、または再帰的にデリファレンスしてツリーオブジェクトにできるオブジェクトコミットオブジェクトをデリファレンスすると、リビジョンのトップディレクトリに対応するツリーオブジェクトが得られます。以下はすべてツリーイッシュです。コミットイッシュ、ツリーオブジェクト、ツリーオブジェクトを指すタグオブジェクト、ツリーオブジェクトを指すタグオブジェクトを指すタグオブジェクトなど。

アンボーン

HEADは、まだ存在せず、コミットもないブランチを指すことができ、そのようなブランチはアンボーンブランチと呼ばれます。ユーザーがアンボーンブランチに遭遇する最も一般的な方法は、他の場所からクローンせずにリポジトリを新規作成することです。HEADは、まだ誕生していないmain(または設定によってはmaster)ブランチを指すことになります。また、一部の操作では、orphanオプションを使用してアンボーンブランチに移動することもできます。

未マージインデックス

未マージのインデックスエントリを含むインデックス

到達不能オブジェクト

ブランチタグ、またはその他の参照から到達可能でないオブジェクト

アップストリームブランチ

対象のブランチにマージされる(または、対象のブランチがリベースされる)デフォルトのブランチ。これはbranch.<name>.remoteおよびbranch.<name>.mergeで設定されます。Aのアップストリームブランチがorigin/Bの場合、「Aorigin/Bを追跡している」と言うことがあります。

作業ツリー

実際にチェックアウトされたファイルのツリー。作業ツリーには通常、HEADコミットのツリーの内容と、まだコミットされていないローカルでの変更が含まれます。

ワークツリー

リポジトリには、ワークツリーがゼロ個(つまりベアリポジトリ)または1つ以上アタッチされています。1つの「ワークツリー」は、「作業ツリー」とリポジトリメタデータで構成されており、そのほとんどは単一リポジトリの他のワークツリーと共有されますが、一部はワークツリーごとに個別に管理されます(例:インデックス、HEAD、MERGE_HEADのような擬似参照、ワークツリーごとの参照、ワークツリーごとの設定ファイル)。

付録A:Gitクイックリファレンス

これは主要なコマンドの簡単なまとめです。前の章で、これらのコマンドがどのように機能するかを詳しく説明しています。

新しいリポジトリの作成

tarballから

$ tar xzf project.tar.gz
$ cd project
$ git init
Initialized empty Git repository in .git/
$ git add .
$ git commit

リモートリポジトリから

$ git clone git://example.com/pub/project.git
$ cd project

ブランチの管理

$ git branch			# list all local branches in this repo
$ git switch test	        # switch working directory to branch "test"
$ git branch new		# create branch "new" starting at current HEAD
$ git branch -d new		# delete branch "new"

現在のHEAD(デフォルト)に基づいて新しいブランチを作成する代わりに、次のコマンドを使用します。

$ git branch new test    # branch named "test"
$ git branch new v2.6.15 # tag named v2.6.15
$ git branch new HEAD^   # commit before the most recent
$ git branch new HEAD^^  # commit before that
$ git branch new test~10 # ten commits before tip of branch "test"

新しいブランチを同時に作成して切り替える

$ git switch -c new v2.6.15

クローン元リポジトリからブランチを更新し、検査する

$ git fetch		# update
$ git branch -r		# list
  origin/master
  origin/next
  ...
$ git switch -c masterwork origin/master

別のリポジトリからブランチをフェッチし、自分のリポジトリに新しい名前を付ける

$ git fetch git://example.com/project.git theirbranch:mybranch
$ git fetch git://example.com/project.git v2.6.15:mybranch

定期的に作業するリポジトリのリストを保持する

$ git remote add example git://example.com/project.git
$ git remote			# list remote repositories
example
origin
$ git remote show example	# get details
* remote example
  URL: git://example.com/project.git
  Tracked remote branches
    master
    next
    ...
$ git fetch example		# update branches from example
$ git branch -r			# list all remote branches

履歴の探索

$ gitk			    # visualize and browse history
$ git log		    # list all commits
$ git log src/		    # ...modifying src/
$ git log v2.6.15..v2.6.16  # ...in v2.6.16, not in v2.6.15
$ git log master..test	    # ...in branch test, not in branch master
$ git log test..master	    # ...in branch master, but not in test
$ git log test...master	    # ...in one branch, not in both
$ git log -S'foo()'	    # ...where difference contain "foo()"
$ git log --since="2 weeks ago"
$ git log -p		    # show patches as well
$ git show		    # most recent commit
$ git diff v2.6.15..v2.6.16 # diff between two tagged versions
$ git diff v2.6.15..HEAD    # diff with current head
$ git grep "foo()"	    # search working directory for "foo()"
$ git grep v2.6.15 "foo()"  # search old tree for "foo()"
$ git show v2.6.15:a.txt    # look at old version of a.txt

回帰を検索する

$ git bisect start
$ git bisect bad		# current version is bad
$ git bisect good v2.6.13-rc2	# last known good revision
Bisecting: 675 revisions left to test after this
				# test here, then:
$ git bisect good		# if this revision is good, or
$ git bisect bad		# if this revision is bad.
				# repeat until done.

変更を加える

Gitが誰を非難すべきかを知っていることを確認する

$ cat >>~/.gitconfig <<\EOF
[user]
	name = Your Name Comes Here
	email = you@yourdomain.example.com
EOF

次のコミットに含めるファイルの内容を選択し、コミットを実行します

$ git add a.txt    # updated file
$ git add b.txt    # new file
$ git rm c.txt     # old file
$ git commit

または、コミットを一度に準備して作成する

$ git commit d.txt # use latest content only of d.txt
$ git commit -a	   # use latest content of all tracked files

マージ

$ git merge test   # merge branch "test" into the current branch
$ git pull git://example.com/project.git master
		   # fetch and merge in remote branch
$ git pull . test  # equivalent to git merge test

変更を共有する

パッチのインポートまたはエクスポート

$ git format-patch origin..HEAD # format a patch for each commit
				# in HEAD but not in origin
$ git am mbox # import patches from the mailbox "mbox"

別のGitリポジトリのブランチをフェッチし、現在のブランチにマージします。

$ git pull git://example.com/project.git theirbranch

フェッチしたブランチをローカルブランチに保存してから、現在のブランチにマージする

$ git pull git://example.com/project.git theirbranch:mybranch

ローカルブランチでコミットを作成した後、リモートブランチを自分のコミットで更新します。

$ git push ssh://example.com/project.git mybranch:theirbranch

リモートブランチとローカルブランチがどちらも「test」という名前の場合

$ git push ssh://example.com/project.git test

頻繁に利用するリモートリポジトリのショートカットバージョン

$ git remote add example ssh://example.com/project.git
$ git push example test

リポジトリのメンテナンス

破損がないか確認する

$ git fsck

再圧縮、不要なごみの削除

$ git gc

付録B:このマニュアルの注意とTODOリスト

TODOリスト

これは進行中の作業です。

基本的な要件

  • 基本的なUNIXコマンドラインを理解しているが、Gitに関する特別な知識を持たない知的な人が、最初から最後まで順番に読めるものでなければなりません。必要であれば、他の前提条件は発生時に具体的に言及する必要があります。

  • 可能な限り、章のタイトルは、説明するタスクを、必要な知識を最小限に抑えた言葉で明確に記述すべきです。例えば、「git am コマンド」ではなく、「プロジェクトにパッチをインポートする」というように。

人々が途中のすべてを読む必要なく重要なトピックにたどり着けるような、明確な章の依存関係グラフを作成する方法を考えること。

Documentation/ をスキャンして、抜け落ちた他のものを探す。特に、

  • ハウツー

  • technical/の一部?

  • フック

  • git[1] のコマンド一覧

メールアーカイブをスキャンして、他に抜け落ちているものがないか確認する。

このマニュアルが提供する以上の背景知識を仮定している man ページがないか確認する。

さらに良い例を追加する。クックブック形式の例だけのセクション全体が良いかもしれません。おそらく「高度な例」セクションを標準的な章の終わりに置くのはどうでしょう?

適切な箇所に用語集への相互参照を含める。

CVS、Subversion、および一連のリリースtarballのインポートを含む、他のバージョン管理システムとの連携に関するセクションを追加する。

プラミングの使用とスクリプトの書き方に関する章を書く。

代替、クローン -reference など。

scroll-to-top