Git
日本語 ▾ トピック ▾ 最新バージョン ▾ ユーザーマニュアルは2.46.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

大規模なプロジェクトの場合、最初のクローン作成には時間がかかる場合がありますが、クローン作成は1回だけですむはずです。

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

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

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

これらのスナップショットは必ずしも古いものから新しいものへと1列に並んでいるわけではありません。代わりに、作業はブランチと呼ばれる並行した開発ラインに沿って同時に進む場合があり、それらはマージと分岐を繰り返します。

単一の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 <ブランチ>

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

git branch <ブランチ> <開始点>

<ブランチ> という名前の新しいブランチを、<開始点> を参照して作成します。<開始点> は、ブランチ名やタグ名など、任意の方法で指定できます。

git branch -d <ブランチ>

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

git branch -D <ブランチ>

マージ状態に関係なく、ブランチ <ブランチ> を削除します。

git switch <ブランチ>

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

git switch -c <新規> <開始点>

<開始点> を参照する新しいブランチ <新規> を作成し、チェックアウトします。

特殊なシンボル「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のコピーです。ただし、そのリポジトリには他のブランチもあった可能性があり、ローカルリポジトリは、これらの各リモートブランチを追跡するブランチ(リモート追跡ブランチと呼ばれます)を保持しており、git-branch[1]-r オプションを使用して表示できます。

$ 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 を直接チェックアウトして、それを調べたり、1回限りのパッチを作成したりすることもできます。デタッチされたHEAD を参照してください。

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

ブランチ、タグ、その他の参照の命名

ブランチ、リモート追跡ブランチ、およびタグはすべてコミットへの参照です。すべての参照は、refs で始まるスラッシュ区切りのパス名で名前が付けられます。これまで使用してきた名前は、実際には省略形です。

  • ブランチ test は、refs/heads/test の省略形です。

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

  • origin/master は、refs/remotes/origin/master の省略形です。

たとえば、同じ名前のタグとブランチが存在する場合は、完全な名前が役立つ場合があります。

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

もう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 <リモート> を実行すると、名前付きの <リモート> のリモート追跡ブランチが更新されます。

.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つの特殊なツールから始めます。

bisectを使用して回帰を見つける方法

プロジェクトのバージョン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が一時的に「(ブランチなし)」に移動したことがわかります。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に、与えられたバージョンが良いか悪いかを伝え、テストする残りのリビジョンの数が毎回ほぼ半分に減っていることに注意してください。

約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:現在のブランチのヘッドを参照します。

他にもたくさんあります。リビジョンに名前を付ける方法の完全なリストについては、gitrevisions[7] のマニュアルページの「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 などの操作では、通常、ORIG_HEADが現在の操作前の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()'

もちろん、これらをすべて組み合わせることもできます。次の例は、Makefile または fs 配下のファイルに変更を加えた v2.5 以降のコミットを検索します。

$ 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

または、低レベルのコマンド git-rev-list[1] を使用して同様の処理を行うこともよくあります。これは、指定されたすべてのコミットの SHA-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] を参照してください。)

ソフトウェアリリースのチェンジログと tarball の作成

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

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

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

1.7.7 より前のバージョンの Git は 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] の「構成ファイル」セクションを参照してください。ファイルはプレーンテキストなので、お気に入りのエディタで編集することもできます。

新しいリポジトリの作成

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

$ 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

変更または削除したファイルをインデックスに追加し、コミットを作成する操作をすべて1ステップで実行できます。

コミットしようとしている内容を把握するために役立つコマンドがいくつかあります。

$ 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] を使用して、コミットの作成、インデックスと作業ツリーのファイルの変更内容の確認、インデックスに含める diff の範囲を個別に選択できます(diff の範囲を右クリックして「コミット用に Hunk をステージ」を選択)。

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

必須ではありませんが、コミットメッセージは、変更内容を要約した短い(50文字以内)1行で始め、その後に空白行、さらに詳しい説明を記述するのが良い習慣です。コミットメッセージの最初の空白行までのテキストはコミットタイトルとして扱われ、Git 全体で使用されます。例えば、git-format-patch[1] はコミットをメールに変換しますが、その際、タイトルを Subject 行に、コミットの残りの部分を本文に使用します。

ファイルの無視

プロジェクトでは、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 は自動的にマージを完了し、結果をコミットします(または、fast-forward の場合は既存のコミットを再利用します。下記参照)。一方、コンフリクトが発生した場合(例えば、リモートブランチとローカルブランチで同じファイルが異なる方法で変更された場合)、警告が表示されます。出力は次のようになる可能性があります。

$ 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方向 diff を実行し、内容が両側から混ざったもののみを表示します(言い換えれば、hunk のマージ結果がステージ 2 のみから来る場合、その部分はコンフリクトしておらず、表示されません。ステージ3についても同様です)。

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

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

$ 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 オプションを使用すると、作業ディレクトリとこれらのステージのいずれかを比較できます。

$ 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 という名前の ref を書き込みます。これは、書き込もうとしているツリーの状態を反映します。自動的にマージできなかったテキストのコンフリクトがあるコンフリクトパスは、作業ツリーと同じようにコンフリクトマーカー付きでこのツリーに書き込まれます。したがって、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

diff は、コンフリクトマーカーとコンテンツ行の両方のバージョンを削除し、代わりに「Goodbye world」を書き込んだことを示しています。

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

$ git log --merge
$ gitk --merge

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

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

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

$ git add file.txt

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

マージの取り消し

行き詰まって、すべてをあきらめて混乱を解消したい場合は、次のコマンドを使用して、マージ前の状態に戻ることができます。

$ git merge --abort

または、削除したいマージを既にコミットしている場合は、

$ git reset --hard ORIG_HEAD

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

Fast-forward マージ

上記で言及されていない特別なケースが1つあり、これは異なる方法で処理されます。通常、マージでは、2つの親を持つマージコミットになります。1つはマージされた開発の2つのラインのそれぞれを指します。

ただし、現在のブランチがもう一方のブランチの祖先である場合(つまり、現在のブランチに存在するすべてのコミットが既に他のブランチに含まれている場合)、Git は「fast-forward」を実行します。現在のブランチのヘッドは、新しいコミットを作成せずに、マージされたブランチのヘッドを指すように前進します。

間違いの修正

作業ツリーを混乱させてしまい、まだ間違いをコミットしていない場合は、次のコマンドを使用して、作業ツリー全体を最後にコミットされた状態に戻すことができます。

$ 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]の「リビジョンの指定」セクションを参照してください。

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

他のタイプのぶら下がりオブジェクト(BLOBとツリー)も可能であり、ぶら下がりオブジェクトは他の状況でも発生する可能性があります。

他の開発者との開発共有

git pullで更新を取得する

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

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

$ git fetch
$ git merge origin/master

ただし、git-pull[1]コマンドを使用すると、これを1つのステップで実行できます。

$ git pull origin master

実際、masterをチェックアウトしている場合、このブランチはgit cloneによって、originリポジトリのHEADブランチから変更を取得するように構成されています。したがって、多くの場合、上記の処理は単純な

$ git pull

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

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

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

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

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

$ git pull . branch
$ git merge branch

はほぼ同等です。

プロジェクトにパッチを送信する

変更点がわずかな場合は、メールでパッチとして送信するのが最も簡単な方法かもしれません。

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

$ git format-patch origin

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

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リポジトリ

プロジェクトに変更を送信する別の方法は、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

(HTTP経由でのプッシュも可能にするWebDAVを使用した、もう少し洗練されたセットアップについては、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で一般的に使用されているモデルに似たモデルを使用することです。このモデルでは、特別な権利を持つ複数の開発者がすべて、単一の共有リポジトリに対してプッシュとプルを行います。この設定方法については、gitcvs-migration[7]を参照してください。

ただし、共有リポジトリに対するGitのサポートには何も問題はありませんが、Gitがサポートする共同作業のモード(パッチを交換し、公開リポジトリからプルする)には、中央の共有リポジトリよりも多くの利点があるため、この動作モードは一般的に推奨されません。

  • Gitはパッチをすばやくインポートおよびマージできるため、単一のメンテナでも非常に高いレートで着信する変更を処理できます。そして、それが多すぎるようになった場合、git pullは、そのメンテナがこのジョブを他のメンテナに委任する簡単な方法を提供しながら、着信する変更のオプションのレビューを許可します。

  • すべての開発者のリポジトリにはプロジェクト履歴の同じ完全なコピーがあるため、特別なリポジトリはなく、相互の合意によって、またはメンテナが反応しなくなったり、一緒に作業するのが困難になったりした場合に、別の開発者がプロジェクトのメンテナンスを引き継ぐのは簡単です。

  • 「コミッター」の中央グループがないということは、「内部」と「外部」の決定を行うための正式な決定の必要性が少ないことを意味します。

リポジトリのWebブラウジングの許可

gitweb cgiスクリプトは、ユーザーがGitをインストールしなくても、プロジェクトのリビジョン、ファイルの内容、およびログを簡単に閲覧できるようにします。RSS/Atomフィードや blame/annotation の詳細などの機能は、オプションで有効にできます。

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

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

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

履歴が切り捨てられた浅いクローンは、プロジェクトの最近の履歴のみに関心があり、アップストリームから完全な履歴を取得するのに費用がかかる場合に役立ちます。

浅いクローンは、git-clone[1] --depthスイッチを指定して作成されます。深さは、git-fetch[1] --depthスイッチで後で変更したり、--unshallowで完全な履歴を復元したりできます。

浅いクローン内でのマージは、マージベースが最近の履歴にある限り機能します。それ以外の場合は、関連性のない履歴をマージするようなもので、大きな競合が発生する可能性があります。この制限により、このようなリポジトリは、マージベースのワークフローでの使用には適さない可能性があります。

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

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

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

  • パッチが最初に配置される「テスト」ツリー。これにより、他の進行中の開発と統合されるときに、パッチが多少公開される可能性があります。このツリーは、Andrewが好きなときに-mmにプルするために利用できます。

  • テスト済みのパッチが移動される「リリース」ツリー。最終的な健全性チェックのため、およびLinusにそれらをアップストリームに送信するための手段として(彼に「プルしてください」というリクエストを送信することによって)。

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

これを設定するには、まず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の履歴に作成する「ノイズ」を嫌っているため、releaseブランチではこれを軽率に行うことを避ける必要があります。これらのノイズの多いコミットは、Linusにリリースブランチからプルするように要求したときに、永続的な履歴の一部になるためです。

いくつかの構成変数(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に送信する「プルしてください」というリクエストメッセージを作成できます。

$ 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命令を使用できます。

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

場合によっては、履歴の奥深くにあるコミットを編集したい場合があります。1つのアプローチは、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]の「インタラクティブモード」セクションを参照してください。

その他のツール

パッチシリーズを維持する目的で、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に存在する関数の1つの意味がコミット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番目の引数は、リモートリポジトリから todo という名前のブランチをフェッチし、refs/heads/my-todo-work という名前でローカルに保存するように Git に指示します。

他のリポジトリからブランチをフェッチすることもできます。例えば、

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

は、example-master という名前の新しいブランチを作成し、指定されたURLにあるリポジトリの master という名前のブランチをそこに保存します。すでに example-master という名前のブランチがある場合、example.comのmasterブランチによって指定されたコミットに高速巻き戻しを試みます。より詳しく説明すると、

git fetchと高速巻き戻し

前の例では、既存のブランチを更新するとき、git fetch は、リモートブランチの最新のコミットが、ブランチのコピーの最新のコミットの子孫であることを確認してから、ブランチのコピーを新しいコミットを指すように更新します。Gitは、このプロセスを高速巻き戻しと呼びます。

高速巻き戻しは、次のようになります。

 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に非高速巻き戻し更新を強制する

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

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

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

$ git fetch -f origin

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

リモート追跡ブランチの設定

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

$ 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」オブジェクトをディレクトリ構造に結び付けます。さらに、treeオブジェクトは他のtreeオブジェクトを参照できるため、ディレクトリ階層が作成されます。

  • 「commit」オブジェクトは、このようなディレクトリ階層をリビジョンの有向非巡回グラフに結び付けます。各コミットには、コミット時のディレクトリ階層を指定する、正確に1つのtreeオブジェクトのオブジェクト名が含まれています。さらに、コミットは、そのディレクトリ階層に到達した経緯を説明する「親」コミットオブジェクトを参照します。

  • 「tag」オブジェクトは、他のオブジェクトをシンボリックに識別し、署名するために使用できます。他のオブジェクトのオブジェクト名とタイプ、シンボリック名(もちろん!)、およびオプションで署名が含まれています。

オブジェクトタイプの詳細

コミットオブジェクト

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

$ 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:特定の時点でのディレクトリの内容を表すtreeオブジェクト(下記で定義)のSHA-1名。

  • 親:プロジェクトの履歴における直前のステップを表す、任意の数のコミットのSHA-1名。上記の例には1つの親があります。マージコミットには複数の親がある場合があります。親のないコミットは「ルート」コミットと呼ばれ、プロジェクトの最初の改訂を表します。各プロジェクトには、少なくとも1つのルートが必要です。プロジェクトには複数のルートがある場合もありますが、それは一般的ではありません(または必ずしも良い考えではありません)。

  • 作成者:この変更を担当する人の名前と、その日付。

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

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

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

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

Treeオブジェクト

汎用性の高いgit-show[1]コマンドを使用してtreeオブジェクトを調べることもできますが、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
...

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

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

(注:サブモジュールが存在する場合、treeはエントリとしてコミットを持つこともできます。ドキュメントについては、サブモジュールを参照してください。)

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

Blobオブジェクト

git-show[1]を使用してblobの内容を調べることができます。例えば、上記のtreeの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です。他のものを参照したり、属性を持ったりすることはありません。

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

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

信頼

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

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

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

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

これを支援するために、Gitはtagオブジェクトも提供します…

Tagオブジェクト

tagオブジェクトには、オブジェクト、オブジェクトタイプ、タグ名、タグを作成した人(「タグ付け者」)の名前、および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-----

tagオブジェクトの作成方法と検証方法については、git-tag[1]コマンドを参照してください。(git-tag[1]は、「軽量タグ」の作成にも使用できます。これは、tagオブジェクトではなく、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」マージ戦略が実行され、交差するマージがあり、それによって複数のマージベースがあることが判明した場合(これはかなりまれですが、発生することがあります)、一時的な中間ツリー(または、交差するマージが多く、複数のマージベースがある場合はさらに多く)が一時的な内部マージベースとして生成されます。これらは実際のオブジェクトですが、最終結果はそれらを指すことはなくなるため、リポジトリ内で「ダングリング」します。

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

コミットの場合、以下を使用できます。

$ 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>

ブロブの内容(または、ツリーの場合は、そのディレクトリの `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

これで、ブロブ 4b9458b3 が欠落しており、ツリー 2d9263c6 がそれを指していることがわかりました。欠落しているブロブオブジェクトのコピーが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 の内容を持つブロブオブジェクトが作成および保存され、そのオブジェクトの 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. インデックスを使用すると、定義されたツリーオブジェクトと作業ツリーを高速に比較できます。

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

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

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

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

インデックスを完全に吹き飛ばした場合でも、それが記述したツリーの名前を持っている限り、一般に情報は失われていません。

サブモジュール

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

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

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

プラス面として、分散型リビジョン管理システムは外部ソースとの統合がはるかに優れています。集中型モデルでは、外部プロジェクトの単一の任意のスナップショットが独自のリビジョン管理からエクスポートされ、ベンダーブランチのローカルリビジョン管理にインポートされます。すべての履歴は非表示になっています。分散型リビジョン管理を使用すると、外部履歴全体をクローンし、開発を追跡し、ローカルな変更を再マージすることがはるかに簡単になります。

Gitのサブモジュール機能を使うと、リポジトリは外部プロジェクトのチェックアウトをサブディレクトリとして含めることができます。サブモジュールは独自に識別情報を保持します。サブモジュール機能は、サブモジュールリポジトリの場所とコミット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> コマンドは、いくつかの処理を行います。

  • 現在のディレクトリの下の指定された <path><repo> からサブモジュールをクローンし、デフォルトでは 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 の主な違いの1つは、git submodule update がブランチの先端ではなく、特定のコミットをチェックアウトすることです。タグをチェックアウトするようなもので、ヘッドがデタッチされるため、ブランチで作業しているわけではありません。

$ git branch
* (detached from d266b98)
  master

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

$ 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>)...]

次に、stdin(パイプまたはファイルからのリダイレクト、または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という特別なヘルパーがあります。

特に「commit」オブジェクトを見ることは有益です。これらは小さく、かなり自明な傾向があるからです。特に、.git/HEADにトップコミット名を置くという慣習に従うと、以下を実行できます。

$ git cat-file commit HEAD

トップコミットが何だったかを確認できます。

複数のツリーのマージ

Gitは3方向マージを実行するのに役立ちます。これは、マージ手順を数回繰り返すことで、多方向マージに使用できます。通常は、1つの3方向マージ(2つの履歴の行を調整する)のみを行い、結果をコミットしますが、必要であれば、複数のブランチを一度にマージできます。

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

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

$ git merge-base <commit1> <commit2>

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

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

ツリーオブジェクトの情報は常にコミットオブジェクトの最初の行にあるからです。

マージする3つのツリー(1つの「オリジナル」ツリー、つまり共通ツリー、および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の出力の各行は、ブロブモードビット、ブロブ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つのステージからのブロブオブジェクトに対して、次のように実行することで、これを解決できます。

$ 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

パスが「unmerged」状態の場合、そのパスに対して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 ファイルは、ファイルのオブジェクト名と一致しないことに注意してください(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型> + <スペース> + <ASCIIの10進数サイズ> + <バイト\0> + <バイナリオブジェクトデータ>のシーケンスを形成するバイトストリームに正常にインフレートされることを検証することで検証できます。

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

Gitのソースコードの鳥瞰図

新しい開発者がGitのソースコードを理解するのは必ずしも簡単ではありません。このセクションでは、どこから始めるかを示すために少しガイダンスを提供します。

始めるのに適した場所は、初期コミットの内容です。以下を使用します。

$ git switch --detach e83c5163

最初のリビジョンは、今日のGitが持つほとんどすべての基盤を築きます(いくつかの場所では詳細が異なる場合もあります)が、一度に読んで理解できるほど小さいです。

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

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

その最初のコミットのアイデアを理解したら、より最近のバージョンをチェックアウトして、read-cache-ll.hobject.h、およびcommit.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はリビジョンウォーカーの元のバージョンであり、常にリビジョンのリストをstdoutに出力しました。これは現在でも機能しており、ほとんどの新しい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 に絞りましょう。なぜなら、

  • これは配管(plumbing)コマンドであり、

  • 最初のコミットにも存在していたからです(文字通り 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表現ではなく、バイナリ表現であることに注意してください。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の説明

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

代替メカニズムにより、リポジトリ は、その オブジェクトデータベース の一部を、別のオブジェクトデータベース(「代替」と呼ばれます)から継承できます。

ベアリポジトリ

ベアリポジトリは通常、リビジョン管理下にあるファイルのローカルにチェックアウトされたコピーを持たない、適切な名前の .git サフィックスが付いた ディレクトリ です。つまり、通常は非表示の .git サブディレクトリに存在するすべてのGit管理ファイルと制御ファイルは、代わりに repository.git ディレクトリに直接存在し、他のファイルは存在せず、チェックアウトもされていません。通常、公開リポジトリの発行者はベアリポジトリを利用可能にします。

blobオブジェクト

型付けされていない オブジェクト、たとえばファイルの内容など。

ブランチ

「ブランチ」は開発ラインです。ブランチの最新の コミット は、そのブランチの先端と呼ばれます。ブランチの先端は、ブランチの ヘッド によって 参照 され、ブランチで追加の開発が行われるにつれて前進します。単一のGit リポジトリ は、任意の数のブランチを追跡できますが、あなたの ワーキングツリー は、そのうちの1つ(「現在」または「チェックアウトされた」ブランチ)のみに関連付けられており、HEAD はそのブランチを指します。

キャッシュ

廃止:インデックス の代替。

チェーン

オブジェクトのリストであり、リスト内の各 オブジェクト は、その後継への参照を含んでいます(たとえば、コミット の後継は、その の1つである可能性があります)。

チェンジセット

BitKeeper/cvsps では「コミット」という意味で使用されます。Gitは変更ではなく状態を格納するため、Gitで「チェンジセット」という用語を使用することは実際には意味がありません。

チェックアウト

オブジェクトデータベース から ツリーオブジェクト または blob を使用して、ワーキングツリー の全部または一部を更新し、ワーキングツリー全体が新しい ブランチ を指している場合は、インデックスHEAD を更新するアクション。

チェリーピック

SCM 用語では、「チェリーピック」とは、一連の変更(通常はコミット)の中から変更のサブセットを選択し、別のコードベースの上にある新しい一連の変更として記録することを意味します。Gitでは、これは「git cherry-pick」コマンドによって実行され、既存の コミット によって導入された変更を抽出し、現在の ブランチ の先端に基づいて新しいコミットとして記録します。

クリーン

ワーキングツリー は、現在の ヘッド によって参照される リビジョン に対応する場合、クリーンです。「ダーティ」も参照してください。

コミット

名詞として:Git履歴の単一のポイント。プロジェクト全体の履歴は、相互に関連するコミットのセットとして表されます。「コミット」という単語は、他のリビジョン管理システムが「リビジョン」または「バージョン」という単語を使用するのと同じ場所でGitによってよく使用されます。コミットオブジェクト の略としても使用されます。

動詞として:プロジェクトの状態の新しいスナップショットをGit履歴に格納するアクション。これは、インデックス の現在の状態を表す新しいコミットを作成し、新しいコミットを指すように HEAD を進めることで行われます。

コミットグラフの概念、表現、および使用法

オブジェクトデータベース内のコミットによって形成される DAG 構造の同義語で、ブランチの先端によって 参照 され、リンクされたコミットの チェーン を使用します。この構造は、決定的なコミットグラフです。グラフは、「コミットグラフ」ファイル など、他の方法で表現できます。

コミットグラフファイル

「コミットグラフ」(通常はハイフンで結ばれます)ファイルは、コミットグラフウォークを高速化する コミットグラフ の補足的な表現です。「コミットグラフ」ファイルは、.git/objects/info ディレクトリまたは代替オブジェクトデータベースの info ディレクトリに保存されます。

コミットオブジェクト

特定の リビジョン に関する情報(、コミッター、作成者、日付、および保存されたリビジョンの最上位の ディレクトリ に対応する ツリーオブジェクト など)を含む オブジェクト

コミットイッシュ(コミッティッシュとも呼ばれます)

コミットオブジェクト、または再帰的に デリファレンス してコミットオブジェクトにできる オブジェクト。以下はすべてコミットイッシュです。コミットオブジェクト、コミットオブジェクトを指す タグオブジェクト、コミットオブジェクトを指すタグオブジェクトを指すタグオブジェクトなど。

コアGit

Gitの基本的なデータ構造とユーティリティ。制限されたソースコード管理ツールのみを公開します。

DAG

有向非巡回グラフ。 コミットオブジェクト は、親を持ち(有向)、コミットオブジェクトのグラフは非巡回的であるため、有向非巡回グラフを形成します(同じ オブジェクト で始まり終わる チェーン はありません)。

宙ぶらりんオブジェクト

他の到達不能なオブジェクトからも 到達可能 でない 到達不能なオブジェクト。宙ぶらりんオブジェクトには、リポジトリ内の参照または オブジェクト から参照がありません。

デリファレンス

シンボリック参照 を参照する場合:シンボリック参照によって指されている 参照 にアクセスするアクション。再帰的なデリファレンスには、非シンボリック参照が見つかるまで、前述のプロセスを結果として得られた参照で繰り返すことが含まれます。

タグオブジェクトを参照する場合:タグが指すオブジェクトにアクセスする操作。タグは、結果のオブジェクトが(該当する場合)指定されたオブジェクトタイプであるか、または"tag"以外のオブジェクトタイプになるまで、結果のオブジェクトに対してこの操作を繰り返すことで再帰的にデリファレンスされます。タグのコンテキストにおける「再帰的デリファレンス」の同義語は「ピール」です。

コミットオブジェクトを参照する場合:コミットのツリーオブジェクトにアクセスする操作。コミットは再帰的にデリファレンスできません。

特に指定がない限り、Gitコマンドまたはプロトコルのコンテキストで使用される「デリファレンス」は暗黙的に再帰的です。

detached HEAD(デタッチされたHEAD)

通常、HEADブランチの名前を格納し、HEADが表す履歴を操作するコマンドは、HEADが指すブランチの先端に至る履歴を操作します。ただし、Gitでは、特定のブランチの先端である必要のない任意のコミットチェックアウトすることもできます。このような状態のHEADは「デタッチされた」と呼ばれます。

HEADがデタッチされている間も、現在のブランチの履歴を操作するコマンド(例:git commitでその上に新しい履歴を構築する)は引き続き機能することに注意してください。これらのコマンドは、ブランチに影響を与えることなく、更新された履歴の先端を指すようにHEADを更新します。現在のブランチについて情報を更新または照会するコマンド(例:git branch --set-upstream-toで現在のブランチが統合するリモート追跡ブランチを設定する)は、この状態では照会する(実際の)現在のブランチがないため、明らかに機能しません。

directory(ディレクトリ)

"ls"で取得できるリスト :-)

dirty(ダーティ)

ワーキングツリーに、現在のブランチコミットされていない変更が含まれている場合、「ダーティ」であると言われます。

evil merge(邪悪なマージ)

邪悪なマージとは、いずれのにも存在しない変更を導入するマージのことです。

fast-forward(早送り)

早送りは、リビジョンがあり、自分が持っているものの派生である別のブランチの変更を「マージ」している場合の特別なタイプのマージです。このような場合、新しいマージコミットを作成するのではなく、マージしているブランチと同じリビジョンを指すようにブランチを更新するだけです。これは、リモートリポジトリリモート追跡ブランチで頻繁に発生します。

fetch(フェッチ)

ブランチをフェッチするとは、リモートリポジトリからブランチのヘッド参照を取得し、ローカルのオブジェクトデータベースにないオブジェクトを見つけ出し、それらも取得することを意味します。git-fetch[1]も参照してください。

file system(ファイルシステム)

Linus Torvaldsは、当初Gitをユーザースペースのファイルシステム、つまりファイルとディレクトリを保持するためのインフラストラクチャとして設計しました。これにより、Gitの効率と速度が確保されました。

Git archive(Gitアーカイブ)

リポジトリの同義語(archの人々にとって)。

gitfile

ワーキングツリーのルートにある、実際のリポジトリであるディレクトリを指すプレーンファイル.git。適切な使用法については、git-worktree[1]またはgit-submodule[1]を参照してください。構文については、gitrepository-layout[5]を参照してください。

grafts(グラフツ)

グラフツを使用すると、コミットの偽の祖先情報を記録することで、本来異なる2つの開発ラインを結合できます。これにより、Gitはコミットが持つのセットを、コミットが作成されたときに記録されたものとは異なるものとして扱うことができます。.git/info/graftsファイルで構成されます。

グラフツメカニズムは古くなっており、リポジトリ間でオブジェクトを転送する際に問題が発生する可能性があることに注意してください。同じことを行うためのより柔軟で堅牢なシステムについては、git-replace[1]を参照してください。

hash

Gitのコンテキストでは、オブジェクト名の同義語です。

head(ヘッド)

ブランチの先端にあるコミットへの名前付き参照。ヘッドは、パックされた参照を使用している場合を除き、$GIT_DIR/refs/heads/ディレクトリのファイルに格納されます。(git-pack-refs[1]を参照してください。)

HEAD

現在のブランチ。より詳しく説明すると:通常、ワーキングツリーは、HEADが参照するツリーの状態から派生します。HEADは、デタッチされたHEADを使用している場合を除き、リポジトリ内のヘッドの1つへの参照であり、デタッチされたHEADの場合、任意のコミットを直接参照します。

head ref(ヘッド参照)

ヘッドの同義語。

hook(フック)

いくつかのGitコマンドの通常の実行中に、開発者が機能やチェックを追加できるようにするオプションのスクリプトへのコールアウトが行われます。通常、フックを使用すると、コマンドを事前に検証して中断したり、操作が完了した後に事後通知を行うことができます。フックスクリプトは$GIT_DIR/hooks/ディレクトリにあり、ファイル名から.sampleサフィックスを削除するだけで有効になります。以前のバージョンのGitでは、それらを実行可能にする必要がありました。

index(インデックス)

状態情報を含むファイルのコレクションで、その内容はオブジェクトとして格納されます。インデックスは、ワーキングツリーの保存されたバージョンです。実際には、マージ時に使用されるワーキングツリーの2番目、さらには3番目のバージョンを含めることもできます。

index entry(インデックスエントリ)

インデックスに格納されている特定のファイルに関する情報。インデックスエントリは、マージが開始されたがまだ終了していない場合(つまり、インデックスにそのファイルの複数のバージョンが含まれている場合)、マージ解除されている可能性があります。

master(マスター)

デフォルトの開発ブランチ。Gitリポジトリを作成すると、"master"という名前のブランチが作成され、アクティブなブランチになります。ほとんどの場合、これにはローカル開発が含まれますが、それは純粋に慣例によるものであり、必須ではありません。

merge(マージ)

動詞として:別のブランチの内容(場合によっては外部リポジトリから)を現在のブランチに持ち込むこと。マージされたブランチが別のリポジトリからのものである場合、これは最初にリモートブランチをフェッチし、その結果を現在のブランチにマージすることによって行われます。このフェッチ操作とマージ操作の組み合わせはプルと呼ばれます。マージは、ブランチが分岐してからの変更を識別し、それらの変更をすべて一緒に適用する自動プロセスによって実行されます。変更が競合する場合、マージを完了するために手動による介入が必要になる場合があります。

名詞として:早送りでない限り、マージが成功すると、マージの結果を表す新しいコミットが作成され、マージされたブランチの先端がになります。このコミットは、「マージコミット」または単に「マージ」と呼ばれることがあります。

object(オブジェクト)

Gitのストレージの単位。コンテンツのSHA-1によって一意に識別されます。したがって、オブジェクトは変更できません。

object database(オブジェクトデータベース)

一連の「オブジェクト」を格納し、個々のオブジェクトオブジェクト名によって識別されます。オブジェクトは通常、$GIT_DIR/objects/に存在します。

object identifier (oid)(オブジェクト識別子(oid))

オブジェクト名の同義語。

object name(オブジェクト名)

オブジェクトの一意の識別子。オブジェクト名は通常、40文字の16進文字列で表されます。SHA-1とも口語的に呼ばれます。

object type(オブジェクトタイプ)

オブジェクトのタイプを記述する識別子の1つである、"コミット"、"ツリー"、"タグ"、または"ブロブ"。

octopus(タコ)

3つ以上のブランチマージすること。

orphan(孤立)

まだ存在しないブランチ(つまり、未誕生のブランチ)に乗る行為。このような操作の後、最初に作成されたコミットは親を持たないコミットになり、新しい履歴が開始されます。

origin(オリジン)

デフォルトの上流リポジトリ。ほとんどのプロジェクトには、少なくとも1つ追跡する上流プロジェクトがあります。デフォルトでは、originがその目的で使用されます。新しい上流の更新は、git branch -rを使用して表示できるorigin/name-of-upstream-branchという名前のリモート追跡ブランチにフェッチされます。

overlay(オーバーレイ)

作業ディレクトリに対して、ファイルの更新と追加のみを行い、削除は行いません。これは、cp -R コマンドがコピー先のディレクトリの内容を更新するのと同様です。これは、checkout コマンドが インデックスまたはtree-ishからファイルをチェックアウトする際のデフォルトモードです。対照的に、no-overlay モードでは、ソースに存在しない追跡対象ファイルも削除します。これは rsync --delete コマンドと同様です。

pack

(容量を節約したり、効率的に転送したりするために)1つのファイルに圧縮されたオブジェクトの集合。

pack index

pack内のオブジェクトの識別子やその他の情報リスト。packの内容に効率的にアクセスするのを補助します。

pathspec

Gitコマンドでパスを制限するために使用されるパターン。

pathspecは、"git ls-files"、"git ls-tree"、"git add"、"git grep"、"git diff"、"git checkout"、その他多くのコマンドのコマンドラインで使用され、操作の範囲をツリーまたは作業ツリーの一部のサブセットに制限します。パスが現在のディレクトリからの相対パスか、トップレベルからの相対パスかは、各コマンドのドキュメントを参照してください。pathspecの構文は次のとおりです。

  • 任意のパスは、それ自身と一致します

  • 最後のスラッシュまでのpathspecは、ディレクトリプレフィックスを表します。そのpathspecの範囲は、そのサブツリーに限定されます。

  • pathspecの残りの部分は、パス名の残りの部分のパターンです。ディレクトリプレフィックスからの相対パスは、fnmatch(3)を使用してそのパターンと照合されます。特に、*?はディレクトリ区切り文字に一致する可能性があります

たとえば、Documentation/*.jpgは、Documentation/chapter_1/figure_1.jpgを含む、Documentationサブツリー内のすべての.jpgファイルに一致します。

コロン:で始まるpathspecは特別な意味を持ちます。短い形式では、先頭のコロン:の後に、0個以上の「マジックシグネチャ」文字(オプションで別のコロン:で終了)が続き、残りがパスと照合するパターンになります。「マジックシグネチャ」は、英数字、グロブ、正規表現の特殊文字、コロンのいずれでもないASCII記号で構成されます。パターンが「マジックシグネチャ」記号セットに属さない文字で始まり、コロンではない場合、オプションのコロンで「マジックシグネチャ」を終了することを省略できます。

長い形式では、先頭のコロン:の後に、開き括弧(、0個以上の「マジックワード」のコンマ区切りリスト、閉じ括弧)が続き、残りがパスと照合するパターンになります。

コロンのみのpathspecは、「pathspecがない」ことを意味します。この形式は、他のpathspecと組み合わせて使用しないでください。

top

マジックワードtop(マジックシグネチャ:/)を使用すると、サブディレクトリ内からコマンドを実行している場合でも、作業ツリーのルートからパターンが照合されます。

literal

パターン内の*?などのワイルドカードは、リテラル文字として扱われます。

icase

大文字小文字を区別しないマッチ。

glob

Gitは、パターンを、FNM_PATHNAMEフラグ付きでfnmatch(3)で消費するのに適したシェルグロブとして扱います。パターン内のワイルドカードは、パス名内の/と一致しません。たとえば、"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つの連続したアスタリスク、そしてスラッシュは、0個以上のディレクトリと一致します。たとえば、"a/**/b"は、"a/b"、"a/x/b"、"a/x/y/b"などに一致します。

  • その他の連続したアスタリスクは無効と見なされます。

    グロブマジックは、リテラルマジックと互換性がありません。

attr

attr:の後には、スペースで区切られた「属性要件」のリストが続き、パスを一致と見なすには、そのすべてを満たす必要があります。これは、通常のマジックなしのpathspecパターンマッチングに加えて行われます。gitattributes[5]を参照してください。

パスの属性要件は、次のいずれかの形式を取ります。

  • "ATTR"は、属性ATTRが設定されていることを要求します。

  • "-ATTR"は、属性ATTRが設定されていないことを要求します。

  • "ATTR=VALUE"は、属性ATTRが文字列VALUEに設定されていることを要求します。

  • "!ATTR"は、属性ATTRが指定されていないことを要求します。

    ツリーオブジェクトに対して照合する場合、属性は、指定されたツリーオブジェクトではなく、作業ツリーから取得されることに注意してください。

exclude

パスが非exclude pathspecに一致した後、すべてのexclude pathspec(マジックシグネチャ:!またはその同義語^)を通過します。一致した場合、パスは無視されます。非exclude pathspecがない場合、除外は、pathspecなしで呼び出されたかのように、結果セットに適用されます。

parent

コミットオブジェクトには、(空の可能性もある)開発系列における論理的な先行オブジェクト、つまり親のリストが含まれています。

peel

タグオブジェクトを再帰的にデリファレンスするアクション。

pickaxe

pickaxeという用語は、特定のテキスト文字列を追加または削除する変更を選択するのに役立つdiffcoreルーチンのオプションを指します。--pickaxe-allオプションを使用すると、特定のテキスト行を追加または削除したフルチェンジセットを表示するために使用できます。git-diff[1]を参照してください。

plumbing

コアGitの愛称。

porcelain

コアGitに依存するプログラムおよびプログラムスイートの愛称。コアGitへの高レベルアクセスを提供します。Porcelainsは、plumbingよりもSCMインターフェースをより多く公開します。

per-worktree ref

グローバルではなく、ワークツリーごとの参照。現在、これはHEADと、refs/bisect/で始まる参照のみですが、後で他の特殊な参照を含めることができます。

pseudoref

通常の参照とは異なるセマンティクスを持つ参照。これらの参照は、通常のGitコマンドで読み取ることができますが、git-update-ref[1]などのコマンドで書き込むことはできません。

Gitでは、次の疑似参照が知られています。

  • FETCH_HEADは、git-fetch[1]またはgit-pull[1]によって書き込まれます。複数のオブジェクトIDを参照できます。各オブジェクトIDには、どこからフェッチされたか、そのフェッチステータスを示すメタデータが注釈付けされています。

  • MERGE_HEADは、マージの競合を解決するときにgit-merge[1]によって書き込まれます。マージ中のすべてのコミットIDが含まれています。

pull

ブランチをプルするとは、それをフェッチしてマージすることを意味します。git-pull[1]も参照してください。

push

ブランチをプッシュするとは、リモートリポジトリからブランチのヘッド参照を取得し、それがブランチのローカルヘッド参照の祖先であるかどうかを確認し、そうである場合、ローカルヘッド参照から到達可能であり、リモートリポジトリにないすべてのオブジェクトをリモートオブジェクトデータベースに配置し、リモートヘッド参照を更新することを意味します。リモートヘッドがローカルヘッドの祖先でない場合、プッシュは失敗します。

reachable

特定のコミットのすべての子孫は、そのコミットから「到達可能」であると言われます。より一般的には、タグからタグ付けするもの、コミットからその親またはツリー、およびツリーからツリーまたはブロブへのチェーンをたどることによって、あるオブジェクトから別のオブジェクトに到達できる場合、オブジェクトは到達可能であると言われます。

reachability bitmaps

到達可能性ビットマップは、オブジェクト検索を高速化するために、パックファイルまたはマルチパックインデックス(MIDX)で選択されたコミットセットの到達可能性に関する情報を格納します。ビットマップは、「.bitmap」ファイルに格納されます。リポジトリで使用できるビットマップファイルは最大で1つです。ビットマップファイルは、1つのパック、またはリポジトリのマルチパックインデックス(存在する場合)のいずれかに属することができます。

rebase

ブランチからの一連の変更を別のベースに再適用し、そのブランチのヘッドを結果にリセットします。

ref

オブジェクト名または別の参照(後者はシンボリック参照と呼ばれます)を指す名前。便宜上、Gitコマンドの引数として使用する場合、参照を省略することがあります。詳細については、gitrevisions[7]を参照してください。参照はリポジトリに格納されます。

参照名前空間は階層構造になっています。参照名は、refs/で始まるか、階層のルートに配置する必要があります。後者の場合、名前は次の規則に従う必要があります。

  • 名前は、大文字またはアンダースコアのみで構成されます。

  • 名前は「_HEAD」で終わるか、「HEAD」と等しくなります。

    これらの規則に一致しない階層のルートには、いくつかの不規則な参照があります。次のリストは網羅的であり、将来拡張されることはありません。

  • AUTO_MERGE

  • BISECT_EXPECTED_REV

  • NOTES_MERGE_PARTIAL

  • NOTES_MERGE_REF

  • MERGE_AUTOSTASH

    異なるサブ階層は、異なる目的で使用されます。例えば、refs/heads/階層はローカルブランチを表すために使用され、refs/tags/階層はローカルタグを表すために使用されます。

reflog

reflog は ref のローカルな「履歴」を示します。言い換えれば、このリポジトリにおける3つ前のリビジョンが何だったか、昨日午後9時14分時点でのこのリポジトリの現在の状態がどうだったかを教えてくれます。詳細は git-reflog[1] を参照してください。

refspec

"refspec" は fetch および push で、リモートの ref とローカルの ref の間のマッピングを記述するために使用されます。詳細は git-fetch[1] または git-push[1] を参照してください。

リモートリポジトリ

同じプロジェクトを追跡するために使用されるが、別の場所に存在する リポジトリ。リモートと通信するには、fetch または push を参照してください。

リモート追跡ブランチ

別の リポジトリからの変更を追跡するために使用される ref。通常、*refs/remotes/foo/bar* のようになり(リモート *foo* の *bar* という名前のブランチを追跡していることを示します)、構成された fetch refspec の右辺と一致します。リモート追跡ブランチには直接変更を含めたり、ローカルコミットを行ったりしないでください。

リポジトリ

ref のコレクションと、ref から 到達可能なすべてのオブジェクトを含むオブジェクトデータベース、および場合によっては1つ以上のポーセリンからのメタデータで構成されます。リポジトリは、代替メカニズムを介して、他のリポジトリとオブジェクトデータベースを共有できます。

解決

失敗した自動マージが残したものを手動で修正するアクション。

リビジョン

コミット(名詞)の同義語。

巻き戻し

開発の一部を破棄すること、つまりheadを以前のリビジョンに割り当てること。

SCM

ソースコード管理(ツール)。

SHA-1

"Secure Hash Algorithm 1"。暗号化ハッシュ関数。Gitの文脈では、オブジェクト名の同義語として使用されます。

浅いクローン

ほとんど浅いリポジトリの同義語ですが、このフレーズは `git clone --depth=...` コマンドを実行して作成されたことをより明確にします。

浅いリポジトリ

浅いリポジトリは、そのコミットの一部がを切り落とされた不完全な履歴を持っています(言い換えれば、Gitはこれらのコミットが親を持たないふりをすることを指示されます。たとえそれらがコミットオブジェクトに記録されていたとしても)。これは、アップストリームに記録された実際の履歴がはるかに大きい場合でも、プロジェクトの最近の履歴のみに関心がある場合に役立つことがあります。浅いリポジトリは、git-clone[1] に `--depth` オプションを指定することで作成され、その履歴は後で git-fetch[1] で深くすることができます。

スタッシュエントリ

将来の再利用のために、ダーティなワーキングディレクトリとインデックスの内容を一時的に格納するために使用されるオブジェクト

サブモジュール

別のリポジトリ(後者はスーパープロジェクトと呼ばれます)内に別のプロジェクトの履歴を保持するリポジトリ

スーパープロジェクト

ワーキングツリー内で他のプロジェクトのリポジトリをサブモジュールとして参照するリポジトリ。スーパープロジェクトは、含まれているサブモジュールのコミットオブジェクトの名前については認識していますが、(コピーは保持していません)。

シンボリック参照

シンボリック参照:SHA-1 ID自体を含むのではなく、*ref: refs/some/thing* の形式であり、参照されると、この参照を再帰的にデリファレンスします。HEAD は、シンボリック参照の代表的な例です。シンボリック参照は、git-symbolic-ref[1] コマンドで操作されます。

タグ

`refs/tags/` 名前空間の下にあるrefで、任意のタイプのオブジェクトを指します(通常、タグはタグまたはコミットオブジェクトを指します)。ヘッドとは対照的に、タグは`commit`コマンドによって更新されません。Gitタグは、Lispタグとは何の関係もありません(Gitのコンテキストでは、オブジェクトタイプと呼ばれます)。タグは、コミットの祖先チェーン内の特定のポイントをマークするために最も一般的に使用されます。

タグオブジェクト

別のオブジェクトを指すrefを含むオブジェクトで、コミットオブジェクトと同様にメッセージを含めることができます。(PGP)署名を含めることもできます。その場合は「署名付きタグオブジェクト」と呼ばれます。

トピックブランチ

開発者が概念的な開発ラインを識別するために使用する通常のGitブランチ。ブランチは非常に簡単で安価であるため、それぞれが明確に定義された概念または小さくても関連する変更を含む、複数の小さなブランチを持つことが望ましい場合がよくあります。

ツリー

ワーキングツリー、またはツリーオブジェクトと、依存するブロブおよびツリーオブジェクト(つまり、ワーキングツリーの格納された表現)のいずれか。

ツリーオブジェクト

関連付けられたブロブオブジェクトおよび/またはツリーオブジェクトへのrefとともに、ファイル名とモードのリストを含むオブジェクトツリーディレクトリと同等です。

tree-ish (treeishとも)

ツリーオブジェクト、または再帰的にデリファレンスしてツリーオブジェクトにできるオブジェクトコミットオブジェクトをデリファレンスすると、リビジョンの最上位ディレクトリに対応するツリーオブジェクトが生成されます。以下はすべてtree-ishです。commit-ish、ツリーオブジェクト、ツリーオブジェクトを指すタグオブジェクト、ツリーオブジェクトを指すタグオブジェクトを指すタグオブジェクトなど。

未誕生

HEADは、まだ存在せず、まだコミットされていないブランチを指すことができ、そのようなブランチは未誕生のブランチと呼ばれます。ユーザーが未誕生のブランチに遭遇する最も一般的な方法は、他の場所からクローンせずに新しいリポジトリを新規作成することです。HEADは、まだ生まれていない *main* (または構成によっては *master*) ブランチを指します。また、一部の操作では、オーファンオプションを使用して未誕生のブランチに移動することができます。

アンマージされたインデックス

アンマージされたインデックスエントリを含むインデックス

到達不能なオブジェクト

ブランチタグ、またはその他の参照から到達可能でないオブジェクト

アップストリームブランチ

問題のブランチにマージされる(または問題のブランチがリベースされる)デフォルトのブランチ。これは、branch.<name>.remote および branch.<name>.merge を介して構成されます。*A* のアップストリームブランチが *origin/B* の場合、「*A* は *origin/B* を追跡している」と言うことがあります。

ワーキングツリー

実際にチェックアウトされたファイルのツリー。ワーキングツリーには通常、HEADコミットのツリーの内容と、まだコミットしていないローカルでの変更が含まれています。

ワークツリー

リポジトリには、ゼロ(つまりベアリポジトリ)または1つ以上のワークツリーをアタッチできます。1つの「ワークツリー」は、「ワーキングツリー」とリポジトリメタデータで構成されており、そのほとんどは単一のリポジトリの他のワークツリー間で共有され、一部はワークツリーごとに個別に管理されます(例:インデックス、HEAD、およびMERGE_HEADのような擬似参照、ワークツリーごとのref、ワークツリーごとの構成ファイル)。

付録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

または、1ステップでコミットを準備して作成する

$ 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]のコマンドリスト

メールアーカイブをスキャンして、他に抜け落ちているものがないか確認します

このマニュアルが提供する以上の背景知識を前提とするものがないか、マニュアルページをスキャンします。

もっと良い例を追加します。クックブックの例だけのセクション全体が良いかもしれません。「高度な例」セクションを章末の標準セクションにすることもできますか?

必要に応じて、用語集への相互参照を含めます。

CVS、Subversion、およびリリースtarballのインポートだけを含む、他のバージョン管理システムとの連携に関するセクションを追加します。

配管の使用とスクリプトの作成に関する章を書きます。

代替、clone -referenceなど。

scroll-to-top