Git ユーザマニュアル (バージョン 1.5.3 以降用)


Table of Contents

Preface
1. リポジトリとブランチ
git リポジトリの取得方法
プロジェクトの他のバージョンのチェックアウト方法
履歴の理解:コミット
履歴の理解:コミット、親、到達可能性
履歴の理解:履歴ダイアグラム
履歴の理解:ブランチとは?
ブランチの運用
新しいブランチを作成せずに古いバージョンを取得する方法
リモートリポジトリのブランチの調査
ブランチ、タグ、その他リファレンスの命名法
git fetch を用いたリポジトリの更新
他のリポジトリからのブランチの取得
2. 履歴の探索
リグレッションを見つける為の bisect の使用方法
コミットの指定方法
タグの作成方法
リビジョンの閲覧方法
差分の生成方法
古いファイルバージョンの参照
ブランチ上のコミット数のカウント
2つのブランチが同じ履歴点にあるかの確認
与えられた fix を含む最初にタグ付けしたバージョンを探す
指定したブランチにだけ存在するコミットを表示する
チェンジログとソフトウェアリリース用の tarball を作成する
指定した中身をもつファイルを参照するコミットを検索する
3. git を使用した開発
git に自分の名前を教える
新規リポジトリの作成
コミットの方法
良いコミットメッセージの書き方
無視するファイル
マージの方法
マージの解決
コンフリクトを解消する為の助けを得る
マージの取り消し
高速前進(Fast-forward)マージ
修正間違い
新しいコミットで間違いを修正する
履歴を再編集して間違いを訂正する
古いバージョンファイルのチェックアウト
作業中の仕事を一時的に脇に片付ける
パフォーマンスを確保する
信頼性の確保
リポジトリの不正を確認する
過去の変更を復旧させる
dangling オブジェクトを試す
4. 他のユーザと開発を共有する
git pull を使用して更新する
プロジェクトにパッチをインポートする
git リポジトリの公開
公開リポジトリの設定
git プロトコル経由での git リポジトリのエクスポート
http経由での git リポジトリのエクスポート
公開リポジトリへ変更を送信する
push に失敗した場合の対処
共有リポジトリの設定
リポジトリのWebブラウジング
Linux のサブシステム管理者が topic ブランチを管理する方法
5. 履歴を再編集し、一連のパッチを管理する
一連の完全なパッチの作成
1つのコミットを再編集する
一連のパッチの並び替えや選択
他のツール
履歴の書き換えによって生じる問題
マージコミットの分割が1本線の履歴の分割よりも困難となる理由
6. 高度なブランチ管理
個々のブランチをフェッチする
git fetch と fast-forwards
強制的に fast-forward を使わずに git fetch を行う
リモートブランチの構成
7. Git のコンセプト
オブジェクトDB
Commit オブジェクト
Tree オブジェクト
Blob オブジェクト
Trust
タグ オブジェクト
gitがどのようにオブジェクトを効率的に保管するか: packファイル
Dangling オブジェクト
リポジトリの破損からの復旧
索引(index)
8. サブモジュール
サブモジュールの落し穴
9. 下位レベルの git 操作
オブジェクトのアクセスと操作
ワークフロー
作業ディレクトリ → 索引
索引 → オブジェクトDB
オブジェクトDB → 索引
索引 → 作業ディレクトリ
機能間の連携
データの中身を参照する
複数のツリーをマージする
複数のツリーをマージする、続き
10. git をハックする
オブジェクトの保管形式
Git ソースコードの鳥瞰図
11. GIT 用語集
12. 付録A:Git Quick Reference
新規リポジトリの作成
ブランチの管理
履歴の探索
変更する
マージ
変更の共有
リポジトリのメンテナンス
13. 付録 B: このマニュアルの覚え書きとTODOリスト

Preface

(訳注:この資料は、http://www.kernel.org/pub/software/scm/git/docs/user-manual.html に掲載されている 内容を日本語訳したものです。 英語が得意でないので、誤訳があるかもしれません。 必要な場合は、原文を参照してください。)

git は高速な分散リビジョン管理システムです。

このマニュアルは、基本的な UNIX コマンドのスキルをもった人が読むことを想定していますが、 git に関する前提知識は必要ありません。

Chapter 1, リポジトリとブランチChapter 2, 履歴の探索 では git を使用してプロジェクトを取得・調査する方法を説明します。 — これらの章を読むことで、ソフトウェアプロジェクトの特定のバージョンをビルドして テストしたり、回帰点を探し出す方法などを習得してください。

実際に開発する必要のある場合は、Chapter 3, git を使用した開発Chapter 4, 他のユーザと開発を共有する も読んでください。

さらに後ろの章では、より特化したトピックスを取り上げます。

包括的なリファレンスドキュメントは man ページ もしくはコマンドgit-help(1)で確認できます。 例えば、"git clone <repo>" のコマンドは次の どちらのようにしても確認できます。

$ man git-clone

$ git help clone

後者のやり方では、マニュアルビューワを選ぶことができます。 詳細はgit-help(1)をご覧ください。

git コマンドの概要を知るには Chapter 12, 付録A:Git Quick Reference を参照してください。

最後に、Chapter 13, 付録 B: このマニュアルの覚え書きとTODOリスト では、このマニュアルをより完全にする為の情報を説明しています。

Chapter 1. リポジトリとブランチ

git リポジトリの取得方法

このマニュアルを読む際には、実験用のリポジトリを取得しておくと便利です。

実験用リポジトリを取得する一番良い方法は git-clone(1) コマンドを使用し、 既存のリポジトリのコピーをダウンロードすることです。

        # git 自身 (ダウンロードサイズは約10MB):
$ git clone git://git.kernel.org/pub/scm/git/git.git
        # linux カーネル (ダウンロードサイズは150MB):
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git

初めてのclone(複製)では大きなプロジェクトの場合、時間をかなり消費するかもしれませんが、 clone は1回行なうだけで良いです。

clone コマンドはそのプロジェクトにちなんだ新しいディレクトリを作成します (上記例では "git" 又は "linux-2.6")。 このディレクトリ内にはプロジェクトファイルのコピーが存在し (これは 作業ツリー といいます)、 それと一緒に ".git" という特別なトップレベルディレクトリが存在します。 ".git" ディレクトリにはプロジェクトの履歴の全てが含まれています。

プロジェクトの他のバージョンのチェックアウト方法

git はファイル集合の履歴を格納するツールとして非常によく考えられたツールです。 git は履歴を圧縮した集合として格納し、 プロジェクトの内容を相互に関連したスナップショットとして格納します。 git ではこれらのバージョンを コミット と呼びます。

それらのスナップショットは必ずしもすべてが古いものから最新のものに 単線で並んでいるとは限りません;その代り、開発ラインが並行して 進むことがあります (これは ブランチ といいます)。 ブランチは分岐し統合されます。

1つの git リポジトリは複数のブランチを追跡することができます。 Git は 各ブランチの最新のコミットの参照を ヘッド の 一覧に保管することで複数のブランチを追跡します; git-branch(1) コマンドはブランチの一覧を表示します:

$ git branch
* master

新しく clone したリポジトリは、1つのブランチヘッド(デフォルトではその名前は "master"です) を持っており、作業ディレクトリは、そのブランチ head が参照するプロジェクトの状態で 初期化されています。

多くのプロジェクトは tags も使用しています。 tag は head のようにプロジェクトの履歴を参照していて、 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
...

tag は常にプロジェクトの特定のバージョンを指し示し、 head は開発が進むに従い更新されていきます。

あるバージョンを指し示す新しいブランチの head を作成し、 そのブランチの head をチェックアウトするには git-checkout(1) を使用します:

$ git checkout -b new v2.6.13

この時、作業ディレクトリは v2.6.13 の tag を付けた時に プロジェクトのもっていた内容が反映されています。 そして、git-branch(1) すると2つのブランチが表示され、 現在チェックアウトしているブランチのマークがアスタリスク(*)で表示されます。

$ git branch
  master
* new

もしバージョン 2.6.17 をむしろ参照したいと考えたなら、 現在のブランチを v2.6.17 を参照するように変更できます。次のようにして:

$ git reset --hard v2.6.17

ある特定の履歴がを参照しているのが、現在のブランチのみの場合、 ブランチをリセットすると、もやはその履歴を参照できなくなることに注意してください; ですから、このコマンドは注意して使用してください。

履歴の理解:コミット

プロジェクトの全ての変更履歴は、commit として表現されます。 git-show(1) コマンドは現在のブランチ上で最後にコミットした 履歴を表示します:

$ git show
commit 17cf781661e6d38f737f15f53ab552f1e95960d7
Author: Linus Torvalds <[email protected].(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" の出力の1行目にその ID が表示されます。 通常、コミットはタグやブランチ名のような短い名前で参照できますが、 この長い名前も役に立つことがあります。特に重要なのは、この ID がこのコミットを 大局的にユニークにしている点です:つまり、他のだれかにその ID を (例えば emailで)伝えた場合、その ID が彼らのリポジトリ内でもあなたのリポジトリ内でも 同じコミットを指し示すことを保障しています。 (彼らのリポジトリにそのコミットが完全に含まれている場合にはです)。 オブジェクト名はコミット対象のコンテンツのハッシュとして計算される為、 変更しない限りは、決して変わらないことが保障されています。

実際、gitのコンセプト では、git に格納されている全ての履歴が、 ファイルデータとディレクトリの中身も含めて、その中身のハッシュの名前で オブジェクトが格納されていることを見るでしょう。

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

全てのコミットは(プロジェクトの最初のコミットを除き)、そのコミットの前に 行われた変更を示す親のコミットを持っています。 親のつながりは最終的にはプロジェクトの開始点まで繋がっています。

しかし、コミットは単純なリストの形式にはなりません; git は分散開発とその統合を許可しており、2つの開発ラインが統合する点は、 "マージ" と呼ばれます。その為、マージを表現するコミットは1つ以上の親を持ち、 各親はその点につながる開発ラインの最新コミットを表現しています。

これがどのように作業するかを見る最良の方法は gitk(1) コマンドを 使用することです;git リポジトリ上で gitk を実行し、マージコミットを 探すことで、git が履歴をどのように整理しているかを理解することができます。

以下では、コミット X がコミット Y の祖先である場合に、 コミット X はコミット Y から "到達可能"(reachable) であると言うことにします。 同様に、Y は X の子孫である、あるいは、コミット Y から コミット X へ 繋がる親のチェーンがあると言うこともできます。

履歴の理解:履歴ダイアグラム

時々 git の履歴を以下のようなダイアグラムを使用して表現することがあります。 コミットは "o" で、コミット間のリンクは - / \ です。 時間は左から右に流れます:

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

特定のコミットについて話をする必要がある時は。記号 "o" は 他の文字や数字に置き換えられることもあります。

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

正確さが必要な時は、用語 "ブランチ" は開発ラインという意味で使用し、 "ブランチのヘッド" (あるいは単に "ヘッド") はブランチ上の最新のコミット という意味で使用します。上記例では、"A" というブランチヘッドは、 ある特定コミットの点を指しますが、その点につながる3つのコミットの線は 全て "ブランチ A" を構成する部品です。

しかしながら、混乱が起きないようなときには、ブランチの場合にも ブランチのヘッドの場合にも単に "ブランチ" ということがあります。

ブランチの運用

ブランチの作成/削除/変更はとても簡単です; 以下にコマンドのサマリを載せます:

git branch
全てのブランチを一覧表示
git branch <branch>
現在のブランチと同じ履歴点を参照する 新しいブランチ <branch> を作成
git branch <branch> <start-point>
<start-point> を参照する新しいブランチ <branch> を作成。 <start-point> にはブランチ名又はタグ名を含む任意の名前を指定できます。
git branch -d <branch>
ブランチ <branch> を削除; 現在のブランチから到達不可能なコミットを削除する場合、 このコマンドは警告を表示して終了します。
git branch -D <branch>
ブランチ <branch> を削除; ブランチのコミットが現在のブランチから到達不可能な場合でも、 他のブランチ又はタグからそのコミットが到達可能な場合があります。 その場合にこのコマンドを使用すると、強制的にブランチを削除することができます。
git checkout <branch>
現在のブランチを <branch> に変更し、作業ディレクトリを <branch> が 参照するバージョンの状態にします。
git checkout -b <new> <start-point>
<start-point> を参照する新しいブランチ <new> を作成し、 そのブランチをチェックアウトします。

特別なシンボル "HEAD" 使用すると、常に現在のブランチを参照することができます。 実際 git は .git ディレクトリにある "HEAD" という名前のファイルを使用して 現在のブランチの場所を記憶しています。

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

新しいブランチを作成せずに古いバージョンを取得する方法

git checkout コマンドは通常はブランチヘッドが引数で渡されることを期待していますが、 任意のコミットを指定することもできます;例えば、 タグによって参照されるコミットをチェックアウトすることができます。

$ git checkout v2.6.17
Note: moving to "v2.6.17" which isn't a local branch
If you want to create a new branch from this checkout, you may do so
(now or later) by using -b with the checkout command again. Example:
  git checkout -b <new_branch_name>
HEAD is now at 427abfa... Linux v2.6.17

この時 HEAD はブランチの代わりにコミットの SHA-1 値を参照しており、 git branch を実行するとブランチにいないことが分かります。

$ cat .git/HEAD
427abfa28afedffadfca9dd8b067eb6d36bac53f
$ git branch
* (no branch)
  master

この状態を HEAD が "切り離されている (detached)" と言います。

この方法は、新しいブランチを作成せずに特定のバージョンを チェックアウトする手軽な方法です。 こうした後でも、後からこのバージョンに対して新しいブランチ(またはタグ)を 作成することができます。

リモートリポジトリのブランチの調査

複製(clone)した時に作成される "master" ブランチは、 複製元リポジトリのヘッドのコピーです。 しかし複製元リポジトリにはそれ以外にもブランチがあるかもしれません。 ローカルリポジトリは、リモート(複製元)ブランチのそれぞれを追跡する為の ブランチを持っています。 それらのブランチは "-r" オプションを付けて git-branch(1) を実行すると 確認できます:

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

それらリモート追跡ブランチを直接チェックアウトすることはできませんが、 自身のブランチを作成しそこで調べることはできます。

$ git checkout -b my-todo-copy origin/todo

"origin" という名前は、複製(clone)したリポジトリを参照する為に git がデフォルトで使用する名前にすぎないことに注意してください。

ブランチ、タグ、その他リファレンスの命名法

ブランチ、リモート追跡ブランチ、タグは全てコミットを参照しています。 全ての参照は "refs" で始まるスラッシュ区切りのパス名が付けられています; これまで使用してきた名前は実のところ全て略記です。

  • ブランチ "test" は "refs/heads/test" の略記です。
  • タグ "v2.6.18" は "refs/tags/v2.6.18" の略記です。
  • "origin/master" は "refs/remotes/origin/master" の略記です。

フルネームは時折役に立つことがあります。例えば、 同じ名前のタグとブランチがあるような場合です。

(新しく作成された参照は .git/refs ディレクトリ内にその参照の 名前で格納されています。しかし、効率性の理由により、 1つのファイルに纏めて圧縮されることもあります;git-pack-refs(1) 参照)

もう一つ役に立つ略記として、あるリポジトリの "HEAD" は、単にそのリポジトリ名 を使うだけで参照できるというのがあります。 例えば "origin" は通常、リポジトリ "origin" の HEAD ブランチの略記を表わします。

参照先として git がチェックするパスの完全なリストと、 同じ略記をもつ複数の参照がある場合の選択規則については git-rev-parse(1) の "SPECIFYING REVISIONS" の節を 確認してください。

git fetch を用いたリポジトリの更新

複製(clone)元の開発者はいずれ自身のリポジトリに変更を加えたり、 新しいコミットを作成したり、新しいコミットを参照したブランチを作成するでしょう。

"git fetch" コマンドは、引数なしの場合、全てのリモート追跡している ブランチを複製元リポジトリの最新バージョンの状態に更新します。 この動作は、自分自身のブランチについては何も変更しません。 — "master"ブランチも同じです、それは複製時にあなたの開発用に 作られたものです。

他のリポジトリからのブランチの取得

複製元以外のリポジトリにあるブランチを追跡することもできます。 そうするには git-remote(1) を使用します:

$ git remote add linux-nfs git://linux-nfs.org/pub/nfs-2.6.git
$ git fetch linux-nfs
* refs/remotes/linux-nfs/master: storing branch 'master' ...
  commit: bf81b46

新しいリモート追跡ブランチは "git remote add" で指定した 省略名で格納され、上記場合は linux-nfs です:

$ git branch -r
linux-nfs/master
origin/master

"git fetch <remote>" をその後に実行すると、<remote> という名前の 追跡ブランチが更新されます。

 .git/config ファイルを見ると、git が新しい節を追加したことを
確認できます。

$ cat .git/config
...
[remote "linux-nfs"]
        url = git://linux-nfs.org/pub/nfs-2.6.git
        fetch = +refs/heads/*:refs/remotes/linux-nfs/*
...

これは、git がリモートブランチを追跡する為に作成したものです; テキストエディタで .git/config を編集し、これらの設定オプションを 変更、削除することもできます。 (詳細は git-config(1) の "CONFIGURATION FILE" を参照してください)

Chapter 2. 履歴の探索

git はファイルの集合の履歴を格納するツールとして とても良く考慮されたツールです。 ファイル階層の中身を圧縮したスナップショットと スナップショット間の関係を表す "commit" を格納することで これを実現しています。

git は非常に柔軟で高速に動作するプロジェクトの履歴探索ツールです。

プロジェクトにバグを入れ込んだコミットを見つける為の便利な 特殊ツールの説明からはじめましょう。

リグレッションを見つける為の 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 は一時的に "(no branch)" に移動していることが確認できます。 HEADはいまやあらゆるブランチから分離されており、 "master" からは到達可能だが、v2.6.18 からは到達できない コミット(id が 65934… のコミット)を指しています。 コンパイルとテストをし、クラッシュするかを確認します。 もしクラッシュするのなら、以下のように:

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

として、より古いバージョンを確認します。このように git に各段階でそのバージョンが good か bad かを伝える作業を続け、 テストすべきリビジョンの残数は各回で約半分ずつに削られていきます。

(今回の場合では)約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 bisect skip

しかしこの場合にgitは結果的いくつかの最初のスキップされたコミットと その後の悪いコミットの間にある最初の悪いコミットが わからなくなる可能性があります。

もし良いコミットと悪いコミットを判断することが可能な テストスクリプトがあるなら、bisect作業の自動化には いくつかの方法があります。 詳細はgit-bisect(1)をご覧ください。

コミットの指定方法

既にコミットの指定方法をいくつか紹介してきました:

  • 40桁の16進数からなるオブジェクト名
  • ブランチ名: 指定したブランチの head を参照します
  • タグ名: 指定したタグが指し示すコミットを参照します (references でブランチとタグの特殊な場合について 見てきました)
  • HEAD:現在のブランチの head を参照します

他にもたくさんあります;リビジョンの呼び方の完全なリストは git-rev-parse(1) の man ページにある "SPECIFYING REVISIONS" の節で 確認できます。

$ git show fb47ddb2 # オブジェクト名の先頭の数文字は
                    # そのコミットを特定するのに通常は十分です。
$ git show HEAD^    # HEAD コミットの親
$ git show HEAD^^   # 祖父母
$ git show HEAD~4   # 祖父母の祖父母

マージコミットは1つ以上の親を持ちます;デフォルトでは ^ と ~ はコミットリストの1つ目の親を指しますが、 次のように指定することもできます;

$ git show HEAD^1   # HEAD の1つ目の親
$ git show HEAD^2   # HEAD の2つ目の親

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..        # v2.5以降のコミット(v2.5から到達不能なコミット)
$ git log test..master  # master から到達可能だが、test からは到達可能でないコミット
$ git log master..test  # test から到達可能だが、master からは到達可能でないコミット
$ git log master...test # test または master から到達可能だが、
                        # 両方からは到達可能でない...
$ git log --since="2 weeks ago" # 最近2週間のコミット
$ git log Makefile      # Makefile を修正しているコミット
$ git log fs/           # fs/ 配下のファイルを修正している...
$ git log -S'foo()'     # 文字列 'foo()' に一致する全てのファイルを
                        # 追加または削除しているコミット

そしてもちろん、これら全てを組み合わせることもできます; 以下は v2.5 以降のコミットで、Makefile 又は fs 配下のファイルを変更している コミットを検索します。

$ git log v2.5.. Makefile fs/

git log を使用し、パッチを表示することもできます:

$ git log -p

他の表示オプションについては git-log(1) の man ページにある "—pretty" オプション を参照してください。

git log は最新のコミットから開始し、親を辿って後方に検索します; しかし、git の履歴は複数の独立した開発ラインを含むことができる為、 一覧表示されるコミットの順番はいくらか任意になります。

差分の生成方法

git-diff(1) を使用すると2つのバージョン間の差分を 生成することができます:

$ git diff master..test

これは2つのブランチの先端の間の差分を表示します。 2つのブランチの共通の祖先から test までの差分を表示したい場合は ドットを2つではなく3つとし、次のようにします:

$ git diff master...test

時には差分ではなく各パッチの集合が必要な場合もあります;その際には git-format-patch(1): を使用します:

$ git format-patch master..test

このようにすると、test から到達可能だが、master からは到達できない各コミットの パッチを含むファイルを生成できます。

古いファイルバージョンの参照

特定のリビジョンをチェックアウトすることで、ファイルの古いバージョンを表示させる ことができます。しかし時にはある1つのファイルの古いバージョンを チェックアウトせずに表示できると便利です; 次のコマンドでそれができます:

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

コロン(:) の前はコミットを指す任意の名前で、その後ろは git が追跡しているファイルの任意のパスです。

ブランチ上のコミット数のカウント

"origin" から分岐した以降に "mybranch" 上で行ったコミットの数を知りたい とします:

$ 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つの異なる履歴ルートによって作られることもありえます。 (訳注:コミットIDは違うが中身が一緒の場合もありえる) 従って、オブジェクト名を比較すべきです:

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

あるいは、"…" のオペレータを使用し、一方からのみ到達可能な 全てのコミットを表示してみることです:つまり、

$ git log origin...master

を行い、2つのブランチが等しい時は、コミットが全く表示されません。

与えられた fix を含む最初にタグ付けしたバージョンを探す

e05db0fd がある問題を解決したコミットであるとし、 その解決を含む最も早いタグ付けされたリリースを探したいとします。

もちろん、その答えは1つ以上あります—コミット e05db0fd 以降に 履歴が分岐しているなら、複数の "最も早い" タグ付けされたリリースが存在します。

e05db0fd 以降のコミットを視覚的に調査することで行えます:

$ gitk e05db0fd..

あるいは git-name-rev(1) を使用し、あるタグに基づいた そのコミットの子孫の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 が e05db0fd を含んでいる場合に限り何も出力をしません、 何故なら v1.5.0-rc1 から到達できないコミットだけが表示されるからです。

As yet another alternative, the git-show-branch(1) command lists the commits reachable from its arguments with a display on the left-hand side that indicates which arguments that commit is reachable from. So, you can run something like (訳注:訳せないので、原文のまま載せます)

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

then search for a line that looks like

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

Which shows that e05db0fd is reachable from itself, from v1.5.0-rc1, and from v1.5.0-rc2, but not from 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

ブランチヘッドの名前を取得し、"master" の行を削除しすることができます。 標準ユーティリティである cut と grep の助けを使用して:

$ 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 のようなコミットを選択する構文の説明は git-rev-parse(1) を参照してください)

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

git-archive(1) コマンドはどのプロジェクトのバージョンからも tar 又は zip アーカイブを作成できます;例えば:

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

これは HEAD を使用し、各ファイルが "project/" が先行する tar アーカイブを 生成します。

ソフトウェアプロジェクトの新しいバージョンをリリースする場合、 リリースアナウンスを含める為、チェンジログを同時に作成したいかもしれません。

Linux 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"

そして、彼はそれらを確認してOKであることを見た後、単に出力されたコマンドを カット&ペーストします。

指定した中身をもつファイルを参照するコミットを検索する

誰かがあなたにファイルのコピーを手渡し、どのコミットがそのように修正し、 コミットの前または後にそのような内容を含んだのかを問い合わせたとします。 その場合、次のようにしてそれを見つけ出します:

$  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 ページが理解の助けになります。

Chapter 3. git を使用した開発

git に自分の名前を教える

コミットをする前に、git に自己紹介をすべきです。 一番簡単な方法はホームディレクトリ下にある .gitconfig というファイルに 次の行が表示されていることを確認することです。

[user]
        name = Your Name Comes Here
        email = [email protected]

(設定ファイルの詳細は git-config(1) の "CONFIGURATION FILE" 節を 参照してください)

新規リポジトリの作成

ゼロから新規リポジトリを作成するのはとても簡単です:

$ mkdir project
$ cd project
$ git init

最初に登録したいものがある場合は (tarball の場合):

$ tar xzvf project.tar.gz
$ cd project
$ git init
$ git add . # ./ 以下にある全てを最初のコミットに含めます
$ git commit

コミットの方法

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

  1. 好きなエディタを使用し、作業ディレクトリに変更を加えます。
  2. 変更したことを git に伝えます。
  3. ステップ2で git に伝えた内容を使用してコミットを作成します。

実際には、ステップ1と2を相互に好きなだけ繰り返すことができます: ステップ3でコミットしたいものの追跡を保つ為、git は "索引(index)" と呼ばれる 特別なエリア内にツリーの中身のスナップショットを保管しています。

最初は索引の中身は HEAD の中身と同じです。 コマンド "git diff —cached" は HEAD と索引間の差分を表示する為、 この時点では何も出力しません。

索引を変更するのは容易です:

索引を新しく修正したファイルの中身で更新するには、以下のようにします。

$ git add path/to/file

新しいファイルの中身を索引に追加するにも、以下のようにします。

$ 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 # HEAD と索引間の差分;
                    # つまり、"commit" を実行したときにコミットされる内容
$ git diff          # 索引と作業ディレクトリ間の差分;
                    # つまり、"commit" を実行したときに含まれない
                    # 変更内容
$ git diff HEAD     # HEAD と作業ツリー間の差分:
                    # つまり、"commit -a" を実行したときにコミットされる内容
$ git status        # 上記のサマリをファイル毎に簡潔に表示

これらのことをするために git-gui(1) を使用することもできます。 git-gui は コミットの作成や、索引と作業ツリー間の差分の参照、 索引に含めるべき差分ハンクを個々に選択する(差分ハンクを右クリックして、 "Stage Hunk For Commit" を選択する) ことができます。

良いコミットメッセージの書き方

必須ではありませんが、格納メッセージを 次のようにするのは良い考えです。1行の短文(50文字未満)で変更のサマリを書き、 その後に空白行を挟んで、最後により綿密な記述をまとめる。 そうすることで、例えばコミットした内容を E-Mail に変更するツールにて、 Subjectに最初の行を使用し、残りの行を本文にすることができます。

無視するファイル

プロジェクトはよく git に追跡してほしく'ない'ファイルを生成します。 典型的なものとしては、ビルドプロセッサーが生成するファイルや、 エディタが生成するバックアップファイルなどです。もちろん、 git が追跡しないファイルに対して git add`をしなければ良いだけの問題です。 しかし、これら追跡しないファイルがいることでイライラさせられることがあります; 例えば、それらファイルに対しての `git add . は実際に不要であるにも、 関わらず、 git status の出力でそれらが表示されてしまいます。

作業ディレクトリのトップレベルに .gitignore という名前のファイルを作成することで、 無視するファイルを git に伝えることができます。

# '#' で始まる行は無視されます
# foo.txt という名前の全てのファイルを無視する
foo.txt
# (生成された) html ファイルは無視する
*.html
# foo.html は例外とし、手でメンテナンスします
!foo.html
# object と archive ファイルは無視する
*.[oa]

記述形式の詳細は gitignore(5) を参照してください。 作業ツリーの他のディレクトリに .gitignore を置くこともできます。 その場合、そのディレクトリとサブディレクトリに適用されます。 .gitignore ファイルは他のファイルと同様、リポジトリに追加することができます (通常と同じで git add .gitignoregit commit を実行するだけです)。 (ビルド時の出力ファイルに一致するパターンのような) 除外パターンを git で管理することは、 あなたのリポジトリを複製する他のユーザにとっても便利なことです。

(プロジェクトの全てのリポジトリの代わりに)ある特定のリポジトリでだけ 除外パターンを適用したい場合は、リポジトリ内の .git/info/exclude という場所に それらを置くか、コンフィグレーション変数 core.excludesfile によって 指定することができます。git コマンドによってはコマンドラインで除外するパターンを 直接指定することもできます。 詳細は gitignore(5) を参照してください。

マージの方法

2つの分散した開発ブランチは git-merge(1) を使用して マージできます:

$ git merge branchname

上記はブランチ "branchname" の開発を現在のブランチにマージします。 コンフリクトが発生した場合は — 例えば、リモートブランチとローカルブランチで 同じファイルが2つの異なる方法で変更された場合 — 警告が表示されます; 出力される内容は以下のようなものです:

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

コンフリクトマーカーは問題のあるファイルに残り、コンフリクトを 手作業で解消した後は、索引をそのファイルの中身で更新し、 git commit を実行することができます。通常の新しいファイルを作成するときと 同じようにです。

gitk を使用しコミット結果を確認すると、それが2つの親を持っていて、 現在のブランチの先頭と、もうひとつのブランチの先頭とを位置している ことが分かります。

マージの解決

マージが自動的に解決されない場合、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

このコンフリクトを解決した後、コミットされる予定の内容は 通常と違って2つの親を持っていることを思い出してください;一方は HEAD、つまり現在のブランチの先端;もう一方はもうひとつのブランチの先端で MERGE_HEAD に格納されています。

マージしている間、索引は各ファイルの3つのバージョンを持っています。 この3つはそれぞれファイルの異なる3つの "ファイルステージ" を表現しています:

$ git show :1:file.txt  # 両方のブランチの共通祖先のファイル
$ git show :2:file.txt  # HEAD にあるバージョン
$ git show :3:file.txt  # MERGE_HEAD にあるバージョン

コンフリクトが起こっている箇所を表示するために git-diff(1)を実行すると、 コンフリクトが起こったマージ結果の間の 三方向の差分(three-way diff)が表示されます。 差分にはステージ2と3双方からきたコンテンツ が混ざった状態で含まれています。 (言い換えるとハンクのマージ結果が ステージ2だけからなされる場合その部分ではコンフリクトは起こらず 表示されません。ステージ3についても同じです)

上記の差分は file.txt の作業ツリーのバージョンとステージ2とステージ3の バージョン間の差分を表示します。その為、各行の先頭に1つの "+" または "-" が 付けられるかわりに、2つの列が使用されます:1つ目は 1つ目の親と作業ディレクトリコピーの間の差分を表すのに利用され、 2つ目は2つ目の親と作業ディレクトリコピーの間の差分を表示するのに利用されます。 (このフォーマットの詳細は git-diff-files(1) の "COMBINED DIFF FORMAT" の 節を参照してください)

コンフリクトを通常と同じ方法で解決した後、(indexの更新前に) 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          # ステージ1との diff
$ git diff --base file.txt      # 上記と同じ
$ git diff -2 file.txt          # ステージ2との diff
$ git diff --ours file.txt      # 上記と同じ
$ git diff -3 file.txt          # ステージ3との diff
$ git diff --theirs file.txt    # 上記と同じ

git-log(1)gitk(1) コマンドもまた merge の手助けをしてくれます。

$ git log --merge
$ gitk --merge

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

linkgit:git-mergetool を利用することもできます。これを利用すると Emacs や kdiff3 のような外部ツールを使用してマージを行うことができます。

ファイルのコンフリクトを解決した後には、索引を更新してください:

$ git add file.txt

すると、そのファイルの各ステージは "崩壊" され、 git diff はもはや (デフォルトでは) そのファイルに対する差分を表示しません。

マージの取り消し

マージ作業に行き詰まり、全ての処置を捨て去る場合には、 いつでもマージ前の状態に戻ることができます。次のようにします。

$ git reset --hard HEAD

あるいは、既に削除したいマージ結果をコミット済みの場合には、次のようにします。

$ git reset --hard ORIG_HEAD

しかし、最後のコマンドは、幾つかの場合に危険となりえます。— そのコミットが 他のブランチにマージされている場合は、決してそのコミットを削除しないでください。 もしそうしたなら、さらにマージする場合に混乱が起きます。

高速前進(Fast-forward)マージ

上記で説明してこなかった特別なケースがあります。 通常マージコミットにおけるマージ結果は2つの親を持ち、 各親はマージした2つの開発ラインのそれぞれを指し示しています。

そのため、現在のブランチが他方の子孫である場合には — つまり 全てのコミットが既に他方のコミットに含まれている場合には — git は "fast forward" を行います;現在のブランチの先頭はマージされるブランチの 先頭の位置に進められ、新しいコミットは作成されません。

修正間違い

作業ツリーに手を入れたが、間違いをまだコミットしていない場合は、 以下のようにして作業ツリーを最後にコミットした状態に戻すことができます。

$ git reset --hard HEAD

コミットした後ですべきではなかったと気が付いた時は、 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-checkout(1) を使用して 特定ファイルの古いバージョンをチェックアウトすると便利な場合があるかもしれません。 これまで branch を切り替える際に git checkout を使用してきましたが、 パス名が与えられた場合には全くことなる動作をします: 次のコマンド

$ git checkout HEAD^ path/to/file

は、path/to/file をコミット HEAD^ の時の内容で置き換え、 索引の更新も行ないます。ブランチは変更しません。

単にそのファイルの古いバージョンを参照したいだけの時は、 git-show(1) を使用すると、作業ディレクトリを修正せずに そのバージョンのファイルを表示できます:

$ git show HEAD^:path/to/file

作業中の仕事を一時的に脇に片付ける

あなたが何か複雑な作業をしている途中に、今の作業とは関係のない明らかなバグを みつけたとします。作業を中断してそのバグを処置したいとします。 git-stash(1) を使用すると、現在の作業状態を保存し、 バグ処置をした後 (あるいは、異なるブランチ上で処置を行い、元に戻り)、 作業中の状態に戻すことができます。

$ git stash save "work in progress for foo feature"

このコマンドはあなたの変更を stash に保存し、 作業ディレクトリをリセットし、索引を現在のブランチの tip に一致 させます。通常の手順でバグの処置をしてください。

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

その後、git stash apply を用いて作業していた時の状態に 戻ることができます。

$ git stash apply

パフォーマンスを確保する

大きなリポジトリでは、git はディスクとメモリの使用量を節約するため、 履歴の情報を圧縮して管理することができます。

この圧縮は自動的には行なわれません。従って 時々 git-gc(1) を実行する必要があります:

$ git gc

アーカイブを圧縮する処理はたくさんの時間がかかるため、 git gc をするときは、他の作業をしない方が良いでしょう。

信頼性の確保

リポジトリの不正を確認する

git-fsck(1) コマンドはリポジトリに対してたくさんの自己一貫性チェックを 実行し、あらゆる問題を報告します。この作業にはいくらかの時間が かかります。最も多い警告は "dangling" オブジェクトに関するものです:

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

Dangling オブジェクトは無害です。悪くとも幾らかのディスクスペースを 余計に消費するだけです。これらは時に紛失した作業内容を復旧させる 最後の機会を与えてくれます — 詳細は the section called “Dangling オブジェクト” を参照してください。

過去の変更を復旧させる

参照ログ(Reflogs)

git-reset(1) —hard を使用してブランチを修正した後に 履歴上でそれを参照しているのがそのブランチであることに気がついたと します。

幸運なことに、git は "reflog" と呼ばれるログを保持しており、 各ブランチの過去全ての値を保持しています。従ってこの場合 古い履歴を例えば次のようにして見つけ出すことができます:

$ git log master@{1}

このコマンドは "master" ブランチヘッドの1つ前のバージョンから到達可能なコミットの一覧を表示します。 この構文は git log 以外にもコミットを引数に持つ任意の git コマンドに利用 できます。以下は例です:

$ git show master@{2}           # 2つ前のブランチの状態を表示
$ git show master@{3}           # 3つ前のブランチの状態を表示
$ gitk master@{yesterday}       # 昨日の状態を表示
$ gitk master@{"1 week ago"}    # 1週間前の状態を表示
$ git log --walk-reflogs master # master に対する reflog エントリを表示します

分割された reflog は HEAD を保つため、

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

は、1週間前に現在のブランチが指していた場所を指すのではなく、 1週間前に HEAD が指していた場所を表示します。 これにより、チェックアウトしていた場所の履歴を確認することができます。

reflog はデフォルトでは30日間保存され、その後削除されます。 git-reflog(1)git-gc(1) を参照すると、 reflog がどのように削除されるかを学ぶことができます。 詳細は git-rev-parse(1) の "SPECIFYING REVISIONS" の節を参照してください。

reflog の履歴は通常の git の履歴と大きく違うことに注意してください。 通常の履歴は同じプロジェクトの各リポジトリ間で共有されますが、 reflog は共有されません:あなたのローカルリポジトリのブランチが時間と ともにどのように変更されたかを説明するだけです。

dangling オブジェクトを試す

いくつかの場面で、reflog を用いても救済できない場合があります。例えば、 ブランチを削除した後に、そこにあった履歴が必要になったような場合です。 この時は reflog もまた削除されます;しかし、リポジトリをまだ削除していない場合は、 git fsck がリポートする dangling オブジェクト内に削除したコミットを見つけられる 場合があります。詳細は the section called “Dangling オブジェクト” を参照してください。

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

これら dangling コミットの一つを例えば以下のようにして見ることができます。

$ gitk 7281251ddd --not --all

これは見たままのことをします:つまり、dangling コミットが表示する コミット履歴のうち、存在する全てのブランチとタグに含まれていないコミットを 表示します。従って、紛失したそのコミットから到達可能な履歴を全て 得ることができます。 (それは単に1つのコミットではないかもしれない点に注意してください: "ラインの先端(tip)"を dangling として報告するだけであり、捨てられた深く複雑な コミットの全てであるかもしれないからです)

履歴を元に戻したい時は、それを参照する新しいブランチを作成してください。 例えば、次のようにします:

$ git branch recovered-branch 7281251ddd

dangling オブジェクトの他の型 (blob や tree)も存在します。 それらは他の状況で発生します。

Chapter 4. 他のユーザと開発を共有する

git pull を使用して更新する

リポジトリを複製し、自分でソースを変更した後には、 元のリポジトリが更新されているかを確認し、自分の作業ディレクトリ上に マージしたいと思うでしょう。

既に git-fetch(1) を用いて 外部追跡ブランチを最新に保つ方法 と2つのブランチをマージする方法を見てきました。 従って、元のリポジトリのマスターブランチの変更をマージすることができます:

$ git fetch
$ git merge origin/master

しかし、git-pull(1) コマンドを使用すれば、1回のステップで この操作を行うことができます。

$ git pull origin master

実際のところ、あなたが "master" をチェックアウトしていたなら、"git pull" はデフォルトでは 元のリポジトリの HEAD ブランチからマージを行ないます。従って たいていは単に以下のようにするだけで、上記のことが出来ます。

$ git pull

より一般的には、リモートブランチで作成されたブランチは、 デフォルトではそのブランチから pull されます。 それらデフォルト値のコントロール方法を理解するには、 git-config(1) の branch.<name>.remote と branch.<name>.merge のオプション の記述と、git-checkout(1)—track オプションの説明を参照してください。

さらに、キータイプを省略する為に "git pull" は pull 元のブランチとリポジトリを説明したデフォルトのコミットメッセージを 生成してくれます。

(しかし、fast forward の場合にはそのようなコミットは 作成されないことに注意してください;その代わり、あなたのブランチには 上流のブランチの最新のコミット位置に更新されます。)

git pull コマンドは "." を "remote" のリポジトリとして扱い、 その場合、単に現在のリポジトリからブランチにマージを行います; 従って次のコマンド

$ git pull . branch
$ git merge branch

は、大雑把に言えば同じです。前者は実際に広く一般に使われています。

プロジェクトにパッチを投稿する

行なった変更を投稿する一番簡単な方法は email でパッチとして それらの変更を送信することです。

初めに、git-format-patch(1) を使用します。;例えば:

$ git format-patch origin

により、カレントディレクトリ内に番号付けされた一連のファイルが生成されます。 それらはカレントブランチには含まれるが origin/HEAD には含まれないパッチです。

これらをあなたのメールクライアントにインポートし、手作業でそれらを 送信できます。しかし、一度にたくさん送信したい時は、むしろ git-send-email(1) スクリプトを使用してこの作業を自動化させたいでしょう。 メーリングリストで助言を求め、あなたのプロジェクトがそのようなパッチを どのように扱うことを望むのか決定すると良いでしょう。

プロジェクトにパッチをインポートする

Git は git-am(1) (am は "apply mailbox(メールボックスを適用する" という意味です) と呼ばれるツールを提供しており、このようなメールされた一連のパッチを インポートすることができます。 パッチが含まれるメッセージ全部を順番に1つの mailbox ファイル、"patches.mbox" と言います、 に保存してください。そして、以下を実行します。

$ git am -3 patches.mbox

Git は各パッチを順番に適用します;もしコンフリクトが見つかった場合は、 適用は中止され、"マージの解決" で説明されているように コンフリクトを解決してください。( "-3" のオプションは git に マージすることを伝えます;もし単純に変更を取り消し、 ツリーと索引を変更したくない場合は、"-3" のオプションを省略してください。)

コンフリクトを解消して索引を更新した後は、 新しいコミットを作成する変わりに、

$ git am --resolved

を実行すると、コミットが生成され、mailbox にある残りのパッチの適用が 再開されます。

最終的には、一連のコミットとなり、一つ一つが元の mailbox の パッチに対応し、各パッチに含まれているメッセージから取得された 著者とコミットログメッセージが利用されます。

git リポジトリの公開

プロジェクトに変更を投稿するもう一つの方法はプロジェクトの管理者に あなたのリポジトリから git-pull(1) を使用して変更を pull してもらうことです。 "git pull を使用して更新する" のセクションで 我々は "main" リポジトリから更新を取得する方法を説明してきましたが、 逆の方向についても同じことができます。

あなたと管理者が同じマシン上にアカウントを持っている場合は、 互いのリポジトリから直接変更を pull することができます; リポジトリの URL を引数として受け取ることのできるコマンドは ローカルのディレクトリ名もまた受け取ることができます:

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

又は、ssh の URL :

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

開発者の少ないプロジェクトや、少ないプライベートなリポジトリを同期 するような場合、これらが必要な全てとなりえます。

しかしながら、より一般的にこれを行なうには、 他のユーザが変更を pull する為の独立した公開リポジトリを(通常は別のホスト上に) 準備する必要があります。このほうが通常はより便利で、こうすることで 個人の作業中の変更と公開する変更とをきれいに分けることができます。

日々の作業は自分の個人用リポジトリ上で行い、 定期的に個人用リポジトリから公開リポジトリに変更を "push" することで 他の開発者は公開リポジトリから変更を pull できるようになります。 従って、変更のフローは、公開リポジトリを持つ別の開発者が1人いるような場合には、 次のようになります:

                             あなたが push
あなたの個人リポジトリ --------------------------> あなたの公開リポジトリ
                                                          |
      |                                                   |
      | あなたが pull                                     | 彼らが pull
      |                                                   |
      |                                                   |
      |                      彼らが push                  V
彼らの公開リポジトリ <--------------------------- 彼らのリポジトリ

次のセクションでどのようにこれを行なうかを説明します。

公開リポジトリの設定

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

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

作成される ディレクトリ proj.git は "裸の(bare)" git リポジトリが含まれています。— すなわち、".git" ディレクトリの中身だけが含まれ、チェックアウトされたファイルは含みません。

次に、proj.git を公開リポジトリのホストとするサーバにコピーします。 scp, rsync その他使いやすいもの何を使っても良いです。

git プロトコル経由での git リポジトリのエクスポート

これは好ましい方法です。

他のだれかがサーバ管理をしている場合は、その人に どこのディレクトリにリポジトリを置くと、git:// URL のどこに現れるかを 教えてもらってください。その場合は以下の説明はスキップして "公開リポジトリへ変更を push する" のセクションに進んでください。

そうでない場合にあなたがすべき事は git-daemon(1) を開始することだけです; このデーモンは 9418 ポートを使用します。デフォルトでは、git ディレクトリと思われ、 git-daemon-export-ok ファイルが存在する全てのディレクトリへのアクセスを許可します。 git daemon の引数にディレクトリのパスを与えることで、 それらパスに対してさらに制限をかけることができます。

git daemon は inetd サービスで動かすこともできます; 詳細は git-daemon(1) を参照してください。 (特に、例 のセクションを参照)

http経由での git リポジトリのエクスポート

git プロトコルはパフォーマンスと信頼性の面でより良いですが、 web サーバが設定されているホストでは、http によるエクスポートの方がより簡単に設定 できるかもしれません。

その場合に行なうべきことは、web サーバが export できるディレクトリ内に 裸の git リポジトリを新規作成し、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から clone 又は pull できるようになっているはずです。 例えば、次のように実行します:

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

(WebDAV を使用することで http で push を行なえるようにすることもできます。 詳細は setup-git-server-over-http を参照してください)

公開リポジトリへ変更を送信する

上記で説明した技術(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 pushfast forward されない場合に 文句を言います;このような場合の対応については次節を参照してください。

"push" のターゲットは通常は bare リポジトリであることに 注意してください。チェックアウトした作業ツリーを持つリポジトリに対しても push することはできますが、push しても作業ツリーは更新されません。 その為、push したブランチが現在チェックアウトしているブランチの場合、 予期しない結果となります!

git fetch と同じように、タイピングを節約する為のオプションを 設定することができます;例えば、次のようにします。

$ cat >>.git/config <<EOF
[remote "public-repo"]
        url = ssh://yourserver.com/~you/proj.git
EOF

こうすることで次のように上記場所に push できるようになります。

$ git push public-repo master

remote.<name>.url, branch.<name>.remote, remote.<name>.push の詳細は git-config(1) のオプションを参照してください。

push に失敗した場合の対処

push の結果がリモートブランチの fast forward とならない場合、 次のようなエラーが発生して push は失敗します:

error: remote 'refs/heads/master' is not an ancestor of
 local  'refs/heads/master'.
 Maybe you are not up-to-date and need to pull first?
error: failed to push to 'ssh://yourserver.com/~you/proj.git'

これは、例えば次のような場合におきます:

  • git reset —hard を使用し、すでに発行していたコミットを削除した場合、または
  • git commit —amend を使用し、すでに発行していたコミットを差し替えた場合 (履歴を再編集して間違いを訂正するの手順で)、または
  • git rebase を使用し、すでに発行したコミットをリベースした場合 (git rebase を使用する の手順で)

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

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

通常、公開したリポジトリ内のブランチヘッドを修正した時はいつでも、 以前指し示していた子孫のコミットを指し示すように変更されます。 このような場合に強制的に push を行うと、規約を壊すことになります) (履歴の書き換えによって生じる問題 を参照)

にもかかわらず、この方法は作業中の一連のパッチを簡単な方法で発行する必要がある場合に 一般に行われる方法であり、あなたがブランチを管理する方針を他の開発者に 伝えている限りは許容できるでしょう。

push が失敗するもう一つのケースとして、同じリポジトリの push 権限を他の人にも 与えている場合が考えられます。その場合、はじめに pull するか rebase によって変更を取得した後に再度 push を試みてください; 詳しくは 次の節gitcvs-migration(7) を参照してください。

共有リポジトリの設定

共同作業をする為のもう一つの方法は CVSで一般に使用されているのと同じような モデルを使用するやり方で、特別な権限を持った複数の開発者が 1つの共有リポジトリに対する push と pull の全てを行なうやり方です。 この場合の設定方法については gitcvs-migration(7) を 参照してください。

しかし、git は共有リポジトリを使った運用で何も問題を起こすことはありませんが、 この運用モードは一般には推奨されません。 それは単に、git がサポートする共同開発のモード(つまり、パッチを交換し、 公開リポジトリからpull する方法)の方が、中央リポジトリよりも多くの点で 利点があるからです:

  • Git はパッチのインポートとマージを高速に行なう能力をもっており、 とても頻繁に変更が行なわれる場合でもそれらのマージを一人の管理者で 行なうことができます。そして、変更が多すぎる場合でも git pull の機能により 寄せられる変更の任意レビューを行なう一方で、変更の受け入れ作業を 他の管理者に容易に委任することができます。
  • 各開発者が専用のリポジトリを持ち、プロジェクトの履歴の完全なコピーを 持つことができるので、どれが特別なリポジトリというわけではないので、 他の開発者がプロジェクトのメンテナンスを引き継ぐのもとても簡単です。
  • "コミッター"と呼ばれる中央のグループが存在しないことにより 誰が "内側" で、だれが "外側" であるかに関する正式な決定を する必要がなくなります。

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

gitweb という CGI スクリプトを使用すると、プロジェクトのファイルと履歴を git をインストールすることなしに参照することができます: gitweb の設定方法は、git のソースツリーに含まれる gitweb/INSTALL ファイルを参照してください。

Linux のサブシステム管理者が topic ブランチを管理する方法

ここでは、Linux カーネルの IA64 アーキテクチャのメンテナンスを担当している Tony Luck の git の利用方法を紹介します。

彼は2つの公開リポジトリを使用します:

  • "test" ツリーはパッチが最初に入れられる場所です。 ここで他の進行中の開発と一緒に統合されて公表されます。 このツリーは Andrew がいつでも -mm に pull できるようにする為のものです。 (訳注:"-mm" とは Andrew Morton によってリリースされる実験的なカーネルパッチ群のこと。 -mm に入って価値を証明されると Linus のツリーに反映される)
  • "release" ツリーはテストされたパッチが最終的に健全であることが確認された後に 移動され、Linus に変更を送付する乗り物として使用されます。(Linus に このツリーを "pull してください" というリクエストをします)

彼は他にも一時的なブランチ("topic branches")を使用します。 それぞれのブランチはパッチの論理的なグループを含んでいます。

この設定をする為には、最初に Linus の公開ツリーを複製することで 作業ツリーを作成します:

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

Linus のツリーは origin/master という名前のリモートブランチに格納され、 git-fetch(1) を使用して更新できます;他の公開ツリーも git-remote(1) を使用して "remote" を設定し git-fetch(1) で それらを最新に保ちます;Chapter 1, リポジトリとブランチ を参照してください。

さて、あなたが作業する為のブランチを作成しました; このブランチは origin/master ブランチの現在の先端から開始していて、 (git-branch(1) の —track オプションを使用して) デフォルトでは Linus からの変更をマージする設定にすべきです。

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

これらは git-pull(1) を用いて簡単に最新に保つことができます。

$ git checkout test && git pull
$ git checkout release && git pull

重要な注意点! これらのブランチにローカルな変更を加えると、 このマージは履歴内にコミットオブジェクトを生成します (ローカルで 変更を加えていない時は単に "Fast forward" でマージされます)。 多くの人は Linux の履歴にこれが作成されることを "ノイズ" として嫌います。 その為 "release" ブランチにこの気まぐれが行なわれるのを避けるべきです。 これらノイズとなるコミットはあなたが Linus にリリースブランチへの pull を依頼するときに恒久的な履歴の一部になってしまいます。

幾つかの設定変数(git-config(1) 参照)は 両方のブランチをあなたの公開ツリーに push するのを容易にしてくれます。 (the section called “公開リポジトリの設定” 参照。)

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

上記により test と release の両方のツリーに git-push(1) を 使用して push することができるようになります:

$ git push mytree

あるいは、test と release のブランチの一方にだけ push する場合は:

$ git push mytree test

あるいは、

$ git push mytree release

さて、コミュニティからの幾つかのパッチを適用することを考えます。 このパッチ(あるいは関連するパッチグループ)を保管するブランチに 短く分かり易い名前を付けます。

$ git checkout -b speed-up-spinlocks origin

パッチを適用し、幾つかのテストを実行し、変更をコミットします。 パッチが複数のパートから構成される場合は、それぞれを分割した コミットとしてこのブランチに適用すべきです。

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

この変更状態が良好なら、"test" ブランチに pull し 公開する準備をします。

$ git checkout test && git pull . speed-up-spinlocks

ここではコンフリクトは発生しそうにありません…しかし この段階までの間にしばらくの時間がかかり、上流から新しいバージョンを pull しているかもしれません。

しばらく後に十分な時間が経ちテストが完了したときに、 同じブランチを "release" ツリーに pull し、上流に向かう準備をします。 これは、それぞれのパッチ(あるいは一連のパッチ)をパッチ用のブランチ に留めておくありがたみを理解する場面です。この作業は一連のパッチが "release" ツリーへ任意の順番で移動できることを意味します。 (訳注: 先を読めばわかりますが、ブランチを作って、パッチを適用すると、パッチ群 のテストをブランチごとに並行して行なうことができます。そして、テストが完了したパッチ のブランチから順に release ツリーへ移動することができます。そして、各パッチ用のブラ ンチの状況も簡単に把握できます。)

$ git checkout release && git pull . speed-up-spinlocks

その後、たくさんのブランチが作成され、それらブランチの名前を 適切に指定していたとしても、それらが何であるか又は何が含まれているかを 忘れてしまうかもしれません。特定のブランチでどんな変更が行なわれているかを 思い出すには、次のようにします:

$ git log linux..branchname | git shortlog

test または release ブランチに既にマージされたかどうかは 次のようにして確認します:

$ git log test..branchname

あるいは

$ git log release..branchname

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

パッチがこの大きなサイクル(test から release に移動し、 Linus に pull され、最終的に自身の "origin/master" ブランチに返却する流れ) を完遂すると変更に対するブランチは不要になります。 このことは次の出力が空であることを確認することで検出できます:

$ git log origin..branchname

この時点でブランチは削除することができます:

$ git branch -d branchname

幾つかの変更は自明で、分割したブランチを作成し test と release ブランチに それぞれマージする必要がない場合もあります。 そういった変更は直接 "release" ブランチに適用し、 "test" ブランチにマージします。

Linus に送る "pull してください" のリクエストに含める diff 状態と 変更の短いサマリを作成するには、次のようにします:

$ git diff --stat origin..release

そして

$ git log -p origin..release | git shortlog

以下はこれら全てをさらに単純化するスクリプトです。

==== update script ====
# GIT ツリーのブランチを更新する。更新すべきブランチが
# origin の場合、kernel.org から pull する。そうでない時は
# origin/master ブランチを test|release ブランチにマージします。

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 ====
# ブランチを test または release ブランチにマージ

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 ====
# ia64 GIT ツリーの状態をレポートする

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

Chapter 5. 履歴を再編集し、一連のパッチを管理する

通常コミットはプロジェクトに追加されるのみで、削除したり置き換えられる ことはありません。Git はこの仮定をもとにデザインされており、 この仮定を破ると git のマージ装置は(例えば)間違ったことをしてしまいます。

しかし、この仮定を破ると便利なシチュエーションもあります。

一連の完全なパッチの作成

あなたが大きなプロジェクトのコントリビュータであったと仮定し、 複雑な変更を加えたとします。あなたはそれを他の開発者に公表する為、 その変更を読みやすい手順にし、それが正しいとわかることを証明し、 各変更を行なった理由がわかるようにしたいとします。

1つのパッチ(あるいはコミット)として変更全てを公表すると、 大き過ぎる為一度に全てを消化できません。

あなたの作業の完全な履歴を公表するとなると、間違いや訂正、意味無く終わったもの などが全て含まれ、冗長すぎてしまいます。

従って、通常は次のような一連のパッチを生成するのが理想的です:

  1. 各パッチが順番に適用できる。
  2. 各パッチは1つの論理的な変更を含み、その変更を説明する メッセージを一緒に含んでいる。
  3. 回帰を持ち込むようなパッチがないこと:一連のパッチの最初の部分だけを 適用した場合でも、コンパイルがとおり、動作し、過去に存在しなかったバグが 持ち込まれない。
  4. 一連のパッチを完全に適用すると、最終結果があなた自身が行なった (おそらく散らかっている!)開発作業の結果と一致する。

これら作業の手助けをする幾つかのツールを紹介し、 それらの使い方を説明し、履歴を再編集することにより発生する問題の幾つかを 説明します

git rebase を使用して一連のパッチを最新に保つ

リモート追跡ブランチ "origin" の上にブランチ "mywork" を作成し、 幾つかコミットを作成したとします:

$ git checkout -b mywork origin
$ vi file.txt
$ git commit
$ vi otherfile.txt
$ git commit
...

mywork にマージをしていないので、変更は "origin" から単純に並行に 行なわれています。

 o--o--o <-- origin
        \
         o--o--o <-- 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

しかし、自分の履歴をマージ操作の無い、単純な一連のコミットの状態で 保ちたいのであれば、その代わりに git-rebase(1) を使用すると 良いでしょう。

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

を実行します。すると、残りのパッチを適用する作業が続けられます。

どの時点でも —abort オプションを使用すると、この作業を取り消し、 rebase を開始する前の mywork の状態に戻ることができます:

$ git rebase --abort

1つのコミットを再編集する

履歴を再編集して間違いを訂正する で見てきたように直前のコミットを 以下のようにして修正することができます。

$ git commit --amend

この操作は、過去のコミットをあなたが変更を受け入れる新しいコミットに 置き換え、過去のコミットメッセージを編集する機会を与えてくれます。

これと git-rebase(1) を組み合わせることで、履歴内のさらに過去のコミットについても 置き換えをし、その先頭の変更に立ち入ることができます。 初めに、次のようにして問題のあるコミットにタグを付けます。

$ git tag bad mywork~5

(gitk または git log が問題のあるコミットを見つけるのに役立ちます。)

そのコミットをチェックアウトして編集し、残りの一連の変更を その先頭にリベースします(ここでは 引き剥がされたhead を 使用する代わりに、一時的なブランチ上にコミットをチェックアウトします。):

$ git checkout bad
$ # ここで変更と索引の更新をします
$ git commit --amend
$ git rebase --onto HEAD bad mywork

これらを行なった後、チェックアウトした mywork が保たれ続け、mywork 上の 先頭のパッチは修正したコミットの先頭に再適用されます。 そして、これらを片付けることができます。

$ git tag -d bad

git の履歴が不変であるという性質は既存のコミットを実際に "変更" していない ことを意味していることに注意してください;代わりに、古いコミットを 新しいオブジェクト名を持つ新しいコミットで置き換えています。

一連のパッチの並び替えや選択

存在するコミットを引数にして git-cherry-pick(1) コマンドを 実行すると、そのコミットが行なった変更を適用し、新しいコミットを 作成することができます。従って、例えば、"mywork" が "origin" の先頭の 一連のパッチを指しているなら、以下のようにすることができます:

$ git checkout -b mywork-new origin
$ gitk origin..mywork &

そして、gitk を使用して mywork ブランチのパッチの一覧を表示し、 cherry-pick を使用して mywork-new にそれらを(可能なら異なる順番で)適用し、 可能であるなら git commit —amend を使用してそれらを修正します。 git-gui(1) コマンドは個々の選択した diff ハンクを索引に含めるかどうかを 選択するのに役に立つかもしれません。(diff ハンクを右クリックし、 "Stage Hunk for Commit" を選択します)

もう一つの技術は git format-patch を使用して一連のパッチを作成し、 パッチの前の状態にリセットすることです:

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

そして、修正し、順番を並び替え、取り除き git-am(1) を使用して 再びパッチを適用します。

他のツール

他にもツールが多数あります。例えば StGIT です。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 は新しい head が古い head の更新されたバージョンであることを認識 しません;Git はこのような状態を2つの開発者が古い head と新しい head で 並行に作業したものとして扱います。 その為、だれかが当たらし head を自身のブランチにマージしようとすると、 git は old を new に置き換える代わりに、その2つの開発ライン(old と new) をいっしょにマージしようとします。 その結果は、期待したものとはことなります。

履歴が再編集されたブランチをまだ公開しようとするかもしれません。 そして、それらブランチをフェッチし順番にテストするのは役に立つことだと 思うかもしれません。しかし、そのようなブランチを自分の作業エリアに pull すべきではありません。

適切なマージをサポートする本来の分散開発では、 公開されたブランチは決して再編集されるべきではありません。

マージコミットの分割が1本線の履歴の分割よりも困難となる理由

git-bisect(1) コマンドはマージコミットを含んだ履歴を正確に扱います。 しかし、コミットがマージコミットである時は、そのコミットが何故問題を起こしている かの原因を見つけ出すのに苦労することがあります。

次の履歴を考えてください:

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

上側の開発ライン上のコミットXにて Zから存在する関数の意味が 変更されたとします。ZからAにつながるコミットは、関数の実装を変更し、 Zの時点で存在する全コール箇所と新しく追加したコール箇所の両方を変更して、 矛盾のない状態にしていたとします。 A の時点ではバグはありません。

それと同時に下側の開発ラインではコミットYにてその関数の新しい コール箇所を追加していたとします。 ZからBにつながるコミットの全ては、その関数の古い動作を想定していて コール元とコール先は互いに矛盾していません。Bにもバグはありません。

2つの開発ラインがきれいにマージできたとすると、 コンフリクトの解消要求はありません。

にもかかわらず、Cのソースは壊れています。なぜなら 下側の開発ラインは上側の開発ラインで行われた新しい動作に変換 されていないからです。 したがって、git-bisect(1) は D に問題があり、 Z には問題ないこと、そしてC が問題の原因であると伝えます。 どのようにしたら、問題が動作変更によるものだと見つけることが できるでしょうか?

git bisect の結果が非マージコミットの場合、通常、単にそのコミットを 確認するだけで問題を見つけることができます。 開発者はコミットを自己完結したより小さいものに分割することで これを容易に行うことができます。しかし、 上記場合には1つのコミットを確認することでは問題が明らかにならない為、 このような方法では解決の役に立ちません;その代わりに開発の大局的な視点が 必要となります。さらに悪いことに、問題となっている関数の動作変更が 上側の開発ラインの単なる小さな部分の変更であるかもしれません。

その一方で、C のマージをする代わりに、ZからBの履歴を Aの先頭 にリベースした場合、次のような1行の履歴を取得することができます:

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

Z と D* の間を git bisect すると、1つの問題の原因となるコミット Y* を見つけることができ、Y* が何故問題を引き起こしている理由を 容易に理解することができるでしょう。

部分的にですが、この理由により、たくさん経験を積んだ git ユーザは マージが頻繁に行われるプロジェクトで作業する場合でさえも 最新の上流バージョンに対するリベースを行うことによって 履歴を1ラインに保つようにしています。

Chapter 6. 高度なブランチ管理

個々のブランチをフェッチする

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 ブランチが与えるコミットの fast-forward を 試みます。詳細は次の節で説明します。

git fetch と fast-forwards

前節の例では、存在するブランチを更新する時に、"git fetch" は リモートブランチの最新のコミットがあなたのコピーしたブランチの最新の コミットの子孫であることを確認してから、新しいコミットの位置に 更新しようとします。 Git はこのプロセスを fast forward と呼びます。

fast forward は、以下のように見えます:

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

時には、新しい head は古い head の子孫ではない可能性があります。 例えば、開発者が深刻な間違いをしたことに気がつき、変更を 元に戻すことにした場合です、その結果は次のようになります:

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

この場合、"git fetch" は失敗し、警告が表示されます。

その場合にも、次の節で説明する方法で、強制的に新しい head に 更新することができます。しかし、上記の場合には それらへの参照を既に作成していない限りは "a" と "b" のコミットが紛失することを意味することに注意してください。

強制的に fast-forward を使わずに git fetch を行う

新しいブランチの head が古い head の子孫ではない為に、 git fetch 失敗した場合には、次のようにして強制的に 更新することができます:

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

"+" 記号を追加していることに注意してください。代わりに "-f" を のフラグを使用し、フェッチした全てのブランチを強制的に更新することもできます:

$ git fetch -f origin

この操作により、example/master の古いバージョンが指していたコミットは紛失して しまうことに注意してください。それは、前の節で説明したとおりです。

リモートブランチの構成

既に説明したように、"origin" はクローン元のリポジトリを参照する ショートカットです。この情報は git の構成ファイルに格納されており、 git-config(1) を使用して参照することができます:

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

他にも頻繁に参照するリポジトリがある場合は、タイピング数を節約するため、 同じように構成ファイルに登録することができます;例えば、

$ git config remote.example.url git://example.com/proj.git

とすると、次の2つのコマンドは同じことをするようになります:

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

さらに、次のオプションを追加すると:

$ git config remote.example.fetch master:refs/remotes/example/master

次のコマンドは全て同じことをするようになります:

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

"+" をつけて強制的に更新することもできます:

$ git config remote.example.fetch +master:ref/remotes/example/master

"git fetch" が example/master 上のコミットを捨て去る可能性があることを 嫌う場合は、この操作はしないでください。

また、上記の構成全ては、git-config(1) を使用する代わりに 直接 .git/config ファイルを編集して登録することもできます。

詳細は git-config(1) 内の 構成オプションについて触れられている 箇所を参照してください。

Chapter 7. Git のコンセプト

Git は少ない数のシンプルだが強力なアイデアで成り立っています。 それらを理解しなくても git を利用することはできますが、 理解することで git をより直感的に理解できます。

最も重要なコンセプトである オブジェクトデータベース索引(index) の説明から開始しましょう、

オブジェクトDB

既に the section called “履歴の理解:コミット” で見てきたように、全てのコミットは 40桁の "オブジェクト名" で格納されています。実際、プロジェクトの履歴を 表現するのに必要な全ての情報は、そのような名前のオブジェクトとして格納されています。 それぞれの名前はオブジェクト内容の SHA-1 ハッシュによって 計算されています。SHA-1ハッシュは暗号学的ハッシュ関数です。 それはつまり、同じ名前を持つ2つの異なるオブジェクトを見つけるのが 不可能であることを意味します。このことは多くの利点を持っています。 とりわけ:

  • Git は2つのオブジェクトが同じであるかどうかを 名前を比較するだけで高速に判断できます。 オブジェクト名が全てのリポジトリに対して同じ方法で計算されるので、 2つのリポジトリに格納した同じ内容は、常に同じ名前で格納されます。
  • オブジェクト名が自身の内容の SHA-1 ハッシュ値と一致しているかを 確認することで、Git はオブジェクトを読み込んだ時に、エラーを検出できます。

(オブジェクトの形式と SHA1 計算の詳細は the section called “オブジェクトの保管形式” を参照してください)

Gitが扱う オブジェクトには4種類あります:"blob", "tree", "commit" そして "tag" です。

  • "blob" オブジェクト はファイルデータを格納するのに使用されます。
  • "tree" オブジェクト は1つ以上の "blob" オブジェクトに リンクし、ディレクトリ構成を作ります。さらに、tree オブジェクトは 他の tree オブジェクトを参照できます。従って、ディレクトリ階層を作成できます。
  • "commit" オブジェクトはディレクトリ階層とリンクし、 リビジョンの 有向非巡回グラフを作ります。— 各コミットは そのコミット時点でのディレクトリ階層を指し示すオブジェクトの名前を 含みます。さらに、commit はそのディレクトリ階層に到った経路を示す "親" のコミットオブジェクトを参照しています。
  • "tag" オブジェクト はあるオブジェクトを特定する シンボルの役目をし、また他のオブジェクトに署名をつける目的でも 利用できます。"tag" オブジェクトは、他のオブジェクトの名前と型、 そして(もちろん)シンボリック名を持ち、時には署名も含んでいます。

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

Commit オブジェクト

"commit" オブジェクトはツリーの物理的な状態にリンクし、 また、どのようにしてその記述に至ったかの情報も一緒に含んでいます。 —pretty=raw オプション付きで git-show(1) または git-log(1) を 実行すると、特定のコミットの内容を確認できます:

$ git show -s --pretty=raw 2be7fcb476
commit 2be7fcb4764f2dbcee52635b91fedb1b3dcf7ab4
tree fb3a8bdd0ceddd019615af4d57a53f43d8cee2bf
parent 257a84d9d02e90447b149af58b271c19405edb6a
author Dave Watson <[email protected]> 1187576872 -0400
committer Junio C Hamano <[email protected]> 1187591163 -0700

    Fix misspelling of 'suppress' in docs

    Signed-off-by: Junio C Hamano <[email protected]>

このように、コミットは次のように定義されています:

  • tree: ある時点のディレクトリの中身を表現する ツリーオブジェクト(以下で定義)の SHA-1 名。
  • parent(s): プロジェクト履歴内のすぐ1つ前の状態を表している 複数のコミットの SHA-1 名。上記例では1つの parent があります; マージコミットでは、1つ以上の場合があります。 parent のないコミットは "root" コミットと呼ばれ、 プロジェクトの初期リビジョンを表します。各プロジェクトは最低1つの root を 持つ必要があります。プロジェクトは複数の root を持つこともできますが、 あまり一般的ではありません(良いアイデアではありません)
  • author: この変更に対する責任者の名前と日付。
  • committer: このコミットを実際に作成した担当者と日付。 committer は author とは一致しないかもしれません、例えば、 author がパッチを作成しそれを E-Mailで別の人がそのパッチをコミットする ことがあります。
  • このコメントに関するコメント。

注意:コミット自身は実際にどのような変更がされたかの情報を持っていません; 全ての変更はコミットが参照しているツリーとparents から連想されるツリーとの比較 によって計算されます。特に、git はファイル名の変更を明示的には記録しようと しません。しかし、同じデータをもつファイルが存在する場合に名前変更であると 認識する方法があります。(例えば、git-diff(1) の -M オプションを参照)

コミットは通常 git-commit(1) によって作成されます。 デフォルトではその親を現在のHEADとしたコミットが作成され、 そのツリーは現在の索引に格納されている内容が使用されます。

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 オブジェクトは名前順でソートされたエントリの一覧を含んでおり、 各エントリは mode, オブジェクトタイプ、SHA-1名、名前を持っています。 tree は 1つのディレクトリツリーの中身を表現します。

オブジェクトタイプが blob の場合はファイルデータであることを表し、 tree である場合は、サブディレクトリであることを表しています。 tree と blob は他のオブジェクトと同じようにその中身の SHA-1値によって 名前が付けられていて、その中身が(全てのサブディレクトリの内容も含めて)同じ場合 にのみ同じ SHA-1 名となります。この仕組みにより git は2つの関連する tree オブジェクト間の差分を高速に調べることができます。

(注意:サブモジュールが存在する場合、tree は commit をエントリに 持つことがあります。Chapter 8, サブモジュール のドキュメントを参照。)

注意:ファイルは全て 644 または 755 のモードとなります:実際、git は 実行パーミッションだけを管理しています。

Blob オブジェクト

git-show(1) を使用すると blob の内容を参照できます; 例として、上記ツリーの blob エントリ "COPYING" を確認します:

$ 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 はそれ自身のデータによって完全に定義されるため、 ディレクトリツリー内(またはリポジトリ内の異なるバージョン)に 2つのファイルがあり、それらが同じ内容であるなら、同じ blob オブジェクトを 共有します。オブジェクトはディレクトリツリーの位置に完全に独立しており、 ファイル名を変更してもそれに対応するオブジェクトは変更されません。

注意:全ての tree と blob オブジェクトは git-show(1) に <revision>:<path> の引数を付けて実行することができます。 これにより、現在チェックアウトしていないツリーの中身を ブラウズすることができます。

Trust

あるソースから blob の SHA-1値と(信頼できないかもしれない)ソースの中身 を受け取ったとします。この場合でも SHA-1値が一致する限りはその内容が 正しいと信頼することができます。何故なら SHA-1値は同じハッシュ値を生成 する異なるファイルを見つけることが困難なように設計されているからです。

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

従ってトップレベルのコミット名を含む 一つの 特定のノートに デジタル署名するだけで、システムに信頼性を持たせることができます。 デジタル署名はあなたがそのコミットを信頼していることを示し、 コミット履歴の不変性は全ての履歴が信頼できることを示しています。

言い換えると、トップレベルのコミットのSHA-1値を伝えるメールを作成し、 GPG/PGPでデジタル署名するだけで、容易に全ての履歴の妥当性を示す ことができるということです。

この仕組みを支援するため、gitは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 <[email protected]> 1171411200 +0000

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

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

タグの作成方法と検証方法は git-tag(1) コマンドを参照してください。 (注意: git-tag(1) は'軽量'タグを作成することもできます。 これは、tagオブジェクトとは全く異なるもので、'refs/tags/'で始まる 単なる参照です)

gitがどのようにオブジェクトを効率的に保管するか: packファイル

新規作成されたオブジェクトは最初はオブジェクトの SHA-1ハッシュ値の 名前でファイルとして保存されます(.git/objects 内に保管されます)。

残念なことに、プロジェクトに大量のオブジェクトが作成されると、 この仕組みでは不十分になります。古いプロジェクトで以下を実行 してみてください:

$ git count-objects
6930 objects, 47620 kilobytes

最初の数字はファイルとして保管されているオブジェクト数です。 二つ目の数字はこれらの "遊離した" オブジェクトによって消費される 容量の合計値です。

これら遊離したオブジェクトを "packファイル" に移動することで ディスク容量を節約し、またgitを高速化することができます。 pack ファイルはオブジェクトのグループを十分に圧縮した形式で保管します: packファイル形式の詳細は technical/pack-format.txt を参照してください。

遊離したオブジェクトを pack に移動するには、git repack を実行するだけです:

$ git repack
Generating pack...
Done counting 6020 objects.
Deltifying 6020 objects.
 100% (6020/6020) done
Writing 6020 objects.
 100% (6020/6020) done
Total 6020, written 6020 (delta 4070), reused 0 (delta 0)
Pack pack-3e54ad29d5b2e05838c75df582c65257b8d08e1c created.

その後、

$ git prune

を実行すると pack に含まれる全ての "遊離した" オブジェクトは削除されます。 この操作は参照されていないオブジェクト(例えば、"git reset" によって コミットを削除した場合などに作成される)も全て削除します 遊離したオブジェクトが削除されたことは .git/objects ディレクトリを 見るか、次のコマンドを実行することで確認できます。

$ git count-objects
0 objects, 0 kilobytes

オブジェクトファイルが削除されても、そのオブジェクトを参照する 全てのコマンドは以前と同じように動作します。

The git-gc(1) コマンドは repack、prune などを実行してくれるので 通常は高レベルであるこのコマンドのみ使用します。

Dangling オブジェクト

git-fsck(1) コマンドは dangling オブジェクトに関する メッセージを表示することがありますが、これは問題ではありません。

dangling オブジェクトが作成される主な原因は ブランチをリベースした場合や、 他のユーザがリベースしたブランチを pull した場合です。 — Chapter 5, 履歴を再編集し、一連のパッチを管理する 参照。この場合、ブランチの古い head は まだ存在していて、head が参照していたオブジェクトも全て残っています。 ブランチのポインタは、他の場所に移しかえられているので、存在しませんが。

dangling オブジェクトが作成される他の例もあります。 例えば、ファイルを "git add" したが、そのファイルに別の変更を加えて コミットしたような場合です。 — この場合、もともと add していた内容は、どのコミットとツリーにも 参照されず、dangling blob オブジェクトとなります。

同様に、"再帰的に" マージを実行した際に、マージ内容が複雑で 複数のマージベースが存在するような場合(あまり発生することはありませんが)には、 中間状態のツリーが一時的に作成されます。これら中間状態の作成時に オブジェクトが作成されますが、それらは最終的なマージ結果では 参照されることがありません。従ってこれらも "dangling" となります。

一般に、dangling オブジェクトが存在しても心配する必要はありません。 どちらかといえば、それらは役に立つものです:何か操作間違いをした時に、 dangling オブジェクトを使用すると元の状態に戻すことができます。 (リベースした後に誤りに気が付いた時、 ある古い dangling の状態に head をリセットすることができます)

commit の場合は、次のようにします:

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

これは、指定したコミットから到達可能だが他のブランチやタグ、その他の参照からは 到達できない履歴すべてを表示します。そのうちのどれかが望むものであるなら、 新しい参照を作成します、例えば次のように。

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

blob と tree の場合は、同じようにはできませんが、 次のようにして確認することができます。

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

これは、blob の中身が何であるか(ツリーの場合、ディレクトリの "ls" の内容) を表示するので、その dangling オブジェクトを残すのにどのような操作が 必要となるかを教えてくれるでしょう。

通常、dangling blob と tree はあまり必要になることはありません。 大抵はコンフリクトマーカのついたマージの途中状態であるか、 "git fetch" を Ctrl+C や何かで中断した際に作成されたもので、 宙ぶらりんで役に立ちません。

いずれにしろ、dangling 状態に興味がないことを確認したのなら、 到達できないオブジェクト全てを破棄することができます:

$ git prune

"git prune" は必ず休止状態にあるリポジトリでだけ実行してください。 — これはファイルシステムの fsck によるリカバリのようなものです: ファイルシステムがマウントされている時には行なうべきではありません。

("git fsck" についてもこれと同じことが言えます。ところで、 git fsck は実際に決してリポジトリを変更することはなく、 検出されたものを報告するだけで、実行しても危険なものではありません。 誰かがリポジトリを変更している最中に実行した場合、 紛らわしく恐ろしいメッセージが表示されますが、悪いことは行なわれません。 それとは反対に "git prune" を他のユーザがリポジトリを変更している最中に 実行するのは 悪い アイデアです。)

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

git は意図的にデータを慎重に扱います。しかし、git 自身にバグがなかったとしても ハードウェアやオペレーティングシステムのエラーによりデータが壊れることがあります。

そのような問題に対する第1の防御はバックアップです。 clone コマンドを使用するか cp, tar または他のバックアップメカニズムを 使用して git ディレクトリをバックアップすることができます。

最後の手段は、破損したオブジェクトを探し出して手作業で置き換えることです。 破損しかかっている最中であっても、作業をする前にリポジトリを バックアップしてください。

1つの blob が紛失または破損している場合を考えます。 そのような場合は時に解決できることがあります。 (ツリー、そして特にコミットの紛失から復旧する場合はより困難です)

開始する前に、破損している箇所を git-fsck(1) を使用して 確認します;これには多大な時間を必要とするかもしれません。

次のように出力されたとします:

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

(通常、これらと一緒に "dangling オブジェクト" のメッセージも表示 されますが、これらは興味深いものではありません)

4b9458b3 の blob が紛失しており、2d9263c6 のツリーが それを参照していることがわかります。その紛失したblobのコピーを 他のリポジトリなどから見つけることができるなら、それを .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
...

これにより、紛失した blob が "myfile" という名前のファイルデータであることが わかります。そして、それがあるディレクトリも特定できたとします— "somedirectory" とします。運よくチェックアウトした作業ツリー内の "somedirectory/myfile" にそれと同じコピーがあるのなら、 git-hash-object(1) を用いてそれが正しいかどうかをテストできます。

$ git hash-object -w somedirectory/myfile

これにより、somedirectory/myfile の内容をもった blob オブジェクトを 作成して格納し、そのオブジェクトの SHA-1 を表示します。 その値が 4b9458b3786228369c63936db65827de3cc06200 であったなら あなたは非常に幸運です。この場合、あなたの推測は正しく、破損は 修復されました!

一致しなかった場合、より詳しい情報が必要になります。 そのファイルのどのバージョンを無くしたかを確認します。

最も簡単な方法は、次のとおりです:

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

生データを出力しているため、次のような出力が得られます

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


commit xyz
Author:
Date:

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

これはすぐ前のファイルのバージョンが "newsha" であり、すぐ後のバージョンが "oldsha1" であることを示しています。 また、oldsha から 4b9458b の変更点と 4b9458b から newsha での変更点 のコミットメッセージについても知ることができます。

変更内容が十分小さい場合、4b9458b の状態の内容を再作成することが できるかもしれません。

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

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

これにより、リポジトリは正常に戻ります!

(ところで、fsck を無視することもできます。次のようにして、

$ git log --raw --all

紛失したオブジェクト(4b9458b..)の sha を探し出します。 - git はたくさんの情報を持っていますが、紛失したのは1つの特定の blob バージョンであるにすぎません)

索引(index)

索引はバイナリファイル(通常 .git/index 内に保管)であり、 ソートされたパス名と、パーミッション、blob の 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. 索引は1つの(ユニークに決定される)ツリーオブジェクトを 生成するのに必要な情報全てを含んでいます。

    例えば、git-commit(1) を実行すると、索引から 新しいコミットが参照する tree オブジェクトを生成し、オブジェクトDBに 格納します。

  2. 索引は索引が定義する tree オブジェクトと 作業ツリーとを高速に比較することができます。

    これは、各エントリに対する追加情報(最終更新日のようなもの)を 格納することで行なわれます。このデータは上記には表示されず、 tree オブジェクトを作成する時には格納されませんが、 変更されているファイルと索引に格納されているファイルを 高速に確認するために使用できます。この仕組みが git がデータの 全てを読み込み変更を探す作業を手助けします。

  3. 異なるツリーオブジェクト間のコンフリクトの情報を表現することができ、 各パス名は3-way マージを行なうときに作成されるツリーの情報を 持つことができます。

    the section called “コンフリクトを解消する為の助けを得る” で見たように、マージしている間、 1つのファイルの("ステージ"と呼ばれる)複数のバージョンを格納する ことができます。git-ls-files(1) の出力の3つ目のカラムは ステージの番号で、コンフリクトしたファイルには0より大きい値が 付けられます。

このように索引は一時的な作業エリアの役割をし、 作業中のツリーの内容が詰められています。

索引を完全に消し去ると、それを記述していたツリーの名前を 持っていない限りは、全ての情報を失ってしまいます。

Chapter 8. サブモジュール

大きなプロジェクトは自己完結したより小さいプロジェクトを含む場合があります。 例えば、組み込みLinuxディストリビュションのソースツリーは ディストリビュション内にローカルに変更を加えられたソフトウェアのピースが 含まれています;ムービープレーヤーは解凍ライブラリの特定のバージョンで ビルドできるようにする必要があるかもしれません;幾つかの独立したプログラムは 同じビルドスクリプトを共有しているかもしれません。

集中型のリビジョン管理システムでは、1つのリポジトリ内に各モジュールを含む ことによってこれを実現します。開発者は全てのモジュールあるいは必要なモジュール だけをチェックアウトすることができます。API や翻訳の移動や更新といった 幾つかのモジュールにまたがったファイルを1回のコミットで変更することができます。

Gitは部分的なチェックアウトを許可していない為、Git の複製アプローチでは 開発者は興味のないモジュールのコピーまで取得しなくてはなりません。 Git は各ディレクトリの変更をスキャンしなくてはならず、 莫大なコミットをチェックアウトすることで、想像以上に Git は遅くなります。

一方プラスの面として、分散型のリビジョン管理システムは外部ソースを より良い形で統合します。集中型モデルでは、外部プロジェクトの1つの任意の スナップショットをそれ自身のリビジョン管理ツールからエクスポートし、 ローカルのリビジョン管理ツールにベンダーブランチとしてインポートします。 変更履歴は全て隠れてしまいます。分散型のリビジョン管理システムでは 外部の履歴全てを複製することができ、よりいっそう容易に開発を進め、 ローカルの変更を再マージすることもできます。

Git のサブモジュール機能は外部のプロジェクトを サブディレクトリとしてリポジトリに含ませることができます。 サブモジュールはそれ自身で独自性をもってメンテナンスされます; これは、サブモジュールは自身のリポジトリとコミットIDとを持つということであり、 そのサブモジュールを含むプロジェクト("親プロジェクト")を複製した場合、 同じリビジョンのサブモジュールを全て容易に複製できます。 親プロジェクトの部分チェックアウトは可能です:サブモジュールを 複製する、しないをサブモジュールごとにしていすることができます。

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> からサブモジュールの複製を作成し、 デフォルトではマスターブランチをチェックアウトします。
  • サブモジュールの複製パスを 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 subumodule update を実行すると、リポジトリの複製と 親プロジェクトにて指定されているコミットをチェックアウトが行われます:

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

git submodule updategit submodule add の間の大きな違いは git submodule update が特定のコミットをチェックアウトするのに対し git submodule add はブランチの先端をチェックアウトするという点です。 それは、タグをチェックアウトするのに似ています: つまり head から引き離され、ブランチ上に位置しません。

$ git branch
* (no branch)
  master

head から引き離されているサブモジュール内で変更を加えたい場合は、 ブランチを作成またはチェックアウトし、変更を加え、サブモジュール内の変更を 公開し、新しいコミットを参照するように親プロジェクトを更新します:

$ git checkout master

または

$ git checkout -b 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 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 にまだ残っています。

ただし、変更をコミットしていない場合は残っていません。

Chapter 9. 下位レベルの 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) で検証することができます。しかし、これら2つは git-tag(1) を 使用すると同じようなことができます。

ワークフロー

git-commit(1)git-checkout(1)git-reset(1) のような高レベルの操作は作業ツリー、索引、オブジェクトDB間の データを移動することによって仕事をします。 Git はこれらの各ステップを独立に行なう下位レベルの操作を提供しています。

一般に、全ての "git" 操作は索引ファイル上で作用します。 いくつかの操作は 純粋に 索引ファイル(現在の索引の状態を表示) 上で作用しますが、ほとんどの操作は索引ファイルとデータベース又は 作業ディレクトリ間のデータを移動します。従って、主に4つの組み合わせがあります:

作業ディレクトリ → 索引

git-update-index(1) コマンドは作業ディレクトリの情報を 索引に更新します。一般には更新したいファイルを特定して 索引の情報を更新します、次のように:

$ git update-index filename

しかし、ファイル名を補完した際などの間違いを防ぐため、 このコマンドは新しいエントリや削除された古いエントリを 自動的に追加することはありません。即ち、存在するキャッシュエントリを単に更新するだけです。

あるファイルを削除する場合は —remove フラグを、 新しいファイルを追加する場合は —add フラグを使用します。

注意! —remove フラグを使用しても必ずしもそのファイルが削除される わけではありません:ファイルがディレクトリ内にまだ存在する場合、 索引は新しいステータスに更新されますが、ファイルは削除されません。 —remove はファイルが削除されたのが正しいことであることが 索引の更新時に考慮されることを意味しているだけであり、 もしファイルが本当に存在しないのなら索引をそれに従い更新するだけです。

git update-index —refresh という特別なオプションもあります。 これは現在の "stat" 情報に一致する各索引の "stat" 情報を リフレッシュします。オブジェクトのステータス自身は更新せず、 オブジェクトが過去に格納されたオブジェクトとまだ一致している かを素早くテストする為に使用されるフィールドだけを更新します。

以前紹介した git-add(1)git-update-index(1) の 単なるラッパースクリプトです。

索引 → オブジェクトDB

現在の索引ファイルを "tree" オブジェクトに書く為には、プログラム

$ git write-tree

を使用します。これにはオプションがありません。— これは その状態を記述する現在の索引をツリーオブジェクトの集合へと 書き出す役目をし、トップレベルのツリーの名前を返却します。 このツリーはいつでも索引を再生成する為に利用できます。

オブジェクトDB → 索引

オブジェクトDBから "tree" ファイルを読み込み、現在の索引を そのツリーの状態と同じにするには次のようにします。 (索引が後で元に戻す可能性がある未保存の状態を含む場合にはこれをしないでください!)

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

これにより索引ファイルは過去に保存したツリーと同じ状態になります。 しかし、それは index ファイルに対してだけです:作業ディレクトリは 何も変更されていません。

索引 → 作業ディレクトリ

次のようにすると、作業ディレクトリを索引の状態に 更新することができます。

$ git checkout-index filename

あるいは、全ての索引をチェックアウトしたい場合は -a を使用します。

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

最後に、一方から他方への純粋な移動ではないものを説明します:

機能間の連携

"git write-tree" で作成したツリーをコミットするには、 そのツリーと背後にある履歴(つまり、その履歴とつながる"親の"コミット) を参照する "commit" オブジェクトを作成します。

通常 "commit" は一つの親を持ちます:これはある変更を行った一つ前の ツリー状態を指しています。しかしながら時には二つ以上の親を持つこと もあります。それは "マージ" と言われる場合であり、そのようなコミットは 二つ以上の状態を一つに纏めます。

言い換えると、ツリーがある作業ディレクトリの特定の状態を表す一方、 コミットはその状態の時刻とそこに至った経緯を表します。

コミットを作成するには、コミット時点の状態を表すツリーと親のリストを 渡すことで作成することができます:

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

コミット時のコメントは標準入力(あるいはパイプやファイルのリダイレクト) で指定します。

git commit-tree は作成したコミットのオブジェクト名を返却します。 通常、この名前は .git/HEAD ファイルに保存され、その結果、 最後のコミットが何であるかを確認できます。

以下は Jon Loeliger が作成したテキストのイラストで、 項目間の関係を示しています。

データの中身を参照する

様々なヘルパーツールを用いることでオブジェクトDBと 索引の中身を確認することができます。 各オブジェクトの中身は git-cat-file(1) で確認します:

$ git cat-file -t <objectname>

これはオブジェクトのタイプを表示し、タイプを確認した後は 次のようにして、中身を確認することができます。

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

注意! Tree はバイナリであり、その中身を見るには git ls-tree という 特別なツールを使用します。これは、バイナリの内容をより読みやすい形式に 変換してくれます。

"commit" オブジェクトの中身を見ることはとても教育的です。 それらは小さく、一目瞭然です。特に、 最新のコミット名が .git/HEAD にあるという規約を使用し、 次のようにすると、最新のコミットの中身を確認できます。

$ git cat-file commit HEAD

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

Git は 3方向マージを行なう手助けをします。これは 任意の回数繰り返すことにより n方向マージに拡張することができます。 通常は3方向マージ(2つの親によるマージ)のみを行ないますが、 複数の親を1回でマージすることもできます。

3方向マージをするには、マージする2組の "commit" オブジェクトが 必要です。これを使用し共通の親(3番目の"commit"オブジェクト)を見つけ、 それら commit オブジェクトを使用してディレクトリ("tree" オブジェクト) の状態を探します。

マージの "ベース" を得るには、まず次のようにして 2つのコミットの共通の親を見つけます。

$ git merge-base <commit1> <commit2>

これは、共通のベースコミットを返却します。 これらコミットの "tree" オブジェクトは次のようにして 容易に見つけることができます。(例えば)

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

tree オブジェクトの情報は、コミットオブジェクトの1行目に 常に記述されています。

マージしようとする3つのツリー(1つは "original"の tree いわゆる 共通の tree、 2つの "結果" tree、いわゆるマージしたいブランチ)を知った後は、 索引に読み込んでそれらを "マージ" します。 古い索引の中身を破棄しなくてはならない場合、エラーを表示します。 従ってそれらをコミットしたか確認してください。— 実際、通常は常に 最後にコミットした内容をもとにマージします(そのコミットは従って 現在の索引の内容と一致します)

マージをするには次のひょうにします。

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

これにより、自明のマージ操作は直接索引ファイルに行なわれ、 git write-tree を用いてその結果を書き込むことができます。

複数のツリーをマージする、続き

残念なことに、多くのマージは自明ではありませんん。 追加や移動、削除されたファイルがある場合や、あるいは両方のブランチ で同じファイルが変更されている場合、"マージエントリ" を含んだ 索引ツリーが残されます。このような索引ツリーは tree オブジェクトには書き込むことができません。その結果を書き込む前に 他のツールを使用してコンフリクトを解消する必要があります。

このような索引状態は git ls-files —unmerged を 用いると確認できます。例えば:

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

git ls-files —unmerged の出力の各行は blob のモードビット、 blob の SHA-1、stage番号'、そしてファイル番号で始まります。 'stage番号 はそれがどの tree から来たかを表す git のやり方です: stage 1 は $orig ツリーに対応し、stege 2 は HEAD の tree stage 3 $target tree です。

既に自明のマージは git read-tree -m にて行なわれたことを 説明しました。例えば、ファイルが HEAD と $target` のどちらでも 変更されていない場合や、HEAD$target が同じ内容の場合は 明らかに最終的な結果は HEAD のものと同じです。 上記の例では hello.cHEAD で変更されていて $target`でも 別の変更が行なわれている場合です。 好きな3方向マージツールを実行してこれを解決することができます。 例えば `diff3`、`merge あるいは git 自身の merge-file などを使用し これら3つのstage 上のファイルを次のようにしてマージします。

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

これはマージ結果は hello.c~2 ファイルに保存し、 コンフリクトがあるときは、コンフリクトマーカが付けられます。 マージ結果を確認した後、このファイルの最終的なマージ結果を git に次のようにして伝えます:

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

ファイルにマージ済みになっていない時は、git update-index は ファイルに解決済みのマークを付けます。

上記は下位レベルにおける git のマージ動作の解説で、 水面下でどのようなことが起きているかの概念を理解する手助けになります。 実際、誰も、git 自身でさえ、git cat-file を3回も実行しません。 git merge-index プログラムがあり、これが stage を一時ファイルとして 抽出し、"merge" スクリプトをコールします:

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

そして、上位レベルとして git merge -s resolve が実装されています。

Chapter 10. git をハックする

この章では git の実装内部の詳細について説明します。 これらの内容はおそらく git の開発者のみが必要となる内容です。

オブジェクトの保管形式

すべてのオブジェクトは静的に決定された "種別" を持っており、 それによりオブジェクトの形式が特定されます(例えば、どのように使用され どのように他のオブジェクトを参照するかなど)。現在のところ、オブジェクト 種別は4つ存在します: "blob", "tree", "commit", "tag" です。

オブジェクトの種別に関係なく、すべてのオブジェクトは次の特徴を持ちます: オブジェクトは全て zlib で圧縮され、ヘッダにはオブジェクトの種別だけでなく、 オブジェクトデータのサイズの情報が含まれています。 なお、オブジェクトの名前に使用されている SHA-1 ハッシュ値は元データ+このヘッダの ハッシュ値となっています。従ってファイルの sha1sum の結果は そのオブジェクトの名前とは一致しません。

その結果、オブジェクトの一般的な一貫性はその中身やオブジェクト種別とは 常に独立してテストすることができます:全ておオブジェクトは次のようにして 検証することができます。(a)それらのハッシュ値がファイルの中身と一致していること。そして、 (b)オブジェクトを解凍することができ、解凍したデータは <オブジェクトの種別> 空白 <サイズ> + <byte\0> + <オブジェクトのバイナリデータ> の順番となっていること。

構造化されたオブジェクトの場合は、さらにその構造と他のオブジェクトの 連結性によって懸賞することができます。これらは git fsck プログラムが 通常行っている内容であり、全オブジェクトの完全な依存グラフを生成し、 それらの内部一貫性を検証します(さらに、単にハッシュを使用した表面的な 一貫性の検証も行います)

Git ソースコードの鳥瞰図

新しい開発者にとっては Git のソースコードの構成を理解するのは必ずしも 容易ではないでしょう。このセクションでは、どこから開始したら良いかを 示す小さなガイダンスです。

手始めとして良い場所は、初期コミットの内容を見ることです。次のように取得します:

$ git checkout e83c5163

初期コミットには現在の git のほとんど全ての基礎ができていますが、 ざっと読みとおすのには十分な小ささです。

現在ではこのリビジョンの頃から用語が変更されています。例えば、 このリビジョンの README では現在 コミット と呼んでいる ものを "チェンジセット" と呼んでいます。

また、現在は "キャッシュ" という言い方はせず それどころか"索引(index)" と呼んでいますが、 ファイルは今も cache.h です。(注釈:Not much reason to change it now, especially since there is no good single name for it anyway, because it is basically the header file which is included by all of Git's C sources.)

初期コミットを眺めて概要を把握できたら、より新しいバージョンを チェックアウトし、cache.h, object.h, commit.h にざっと目を 通してください。

初期の頃には、Git は UNIX の伝統にならい、非常にシンプルなプログラムの 集まりとなっていて、それらをパイプで組み合わせ、スクリプト化したりして 利用するようなツールでした。新しいことを試すのが容易であったため、 初期の開発でこれは良いことでした。しかし、現在では、これらの大部分は 内部に組み込まれ、いくつかのコア部分は "ライブラリ化" されました。 つまり、パフォーマンスや移植性、コードの重複の回避などの理由で libgit.a に入れられました。

ここまでの確認で、索引が何かを理解し、そして、cashe.h`内の索引のデータ 構造を理解することができたはずです。また、4つのオブジェクトタイプ(blob, tree, commit, tag)が `struct object の共通構造を継承していていることや、 struct object がそれらの最初のメンバであり 例えば (struct object *)commit のようにキャストすることで &commit->object と同じ結果が得られること、同じようにオブジェクト名やフラグも 得られることを確認できたはずです。

ここまでで、オブジェクトデータに関する確認はひと段落です。

次のステップ:オブジェクトの指定方法を理解してください。 コミットの指定方法 に書かれているとおり、 オブジェクトの指定方法は数個です(そして、それらはコミットだけに限ったことではありません!)。 これらの指定部分の実装は sha1_name.c で行われています。 その中の関数 get_sha1() をざっとみてください。 特徴的な部分は `get_sha1_basic() などの関数部分でハンドリングされています。

この部分は、Git の最もライブラリ化された部分、すなわちリビジョンウォーカー に対するよい導入になっています。

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

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

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

git rev-list はリビジョンウォーカーの元になったバージョンであり、 常に リビジョンの一覧を標準出力に出力するコマンドです。 このコマンドは現在でも役に立っており、新しい Git プログラムを スクリプトとして作成するような場合によく利用されます。

git rev-parse はもはや重要ではありません;何故ならこれは、スクリプトにより コールされる異なる配管コマンドに対して、関連のあるオプションをフィルタリング する為だけに利用されているからです。

git rev-list が行っていることのほとんどは、revision.crevision.h に含まれています。rev_info という名前の構造内のオプションを包み込み、 どのようにどのリビジョンにウォーク(アクセス)するかなどのコントロールをします。

git rev-parse の元の役割は、現在は setup_revisions() 関数に 取って代わられました。setup_revisions() はリビジョンウォーカーの為に リビジョンと共通のコマンドラインの解析をします。解析結果は rev_info に 保管されて利用されます。setup_revisions() を呼び出した後に、自身の コマンドラインオプションを解析することもできます。その後に prepare_revision_walk() をコールして初期化すると、get_revision() 関数にて ここのコミットを取得することができます。

リビジョンウォーカーの処理をより詳しく知りたい場合は、始めに cmd_log() の 実装を参照してください;git show v1.3.0~155^2~4 を実行し、その関数まで スクロールしてください(もはや setup_pager() を直接コールする必要がない 点に注意してください)。

現在では git log はビルトイン化されています(つまり、git コマンド内に含まれています)。 ビルトイン化されているソース部分は

  • cmd_<bla> という名前の関数で、通常は builtin-<bla>.c 内で定義されていて、 宣言は builtin.h 内で行われています。
  • git.c 内の commands[] 配列のエントリと、
  • Makefile 内の BUILTIN_OBJECTS 内のエントリとがあります。

時には1つのソースファイルに複数のビルトインが含まれていることがあります。 例えば、cmd_whatchanged()cmd_log() はコードのかなりを共有しているため、 両方とも builtin-log.c 内に存在します。このような場合、コマンドは .c ファイルの名前で呼ばれず、Makefile 内の BUILT_INS 内に リストされている必要があります。

git log は 元のスクリプトがしていた内容よりも C の実装の方がより 複雑に見えますが、より高い柔軟性とパフォーマンス性を持っています。

ここまでで再び一息つくのにちょうど良い箇所です。

レッスン3はコードの観察です。(基本的な概念を学んだ後に) Git の構成を学ぶには これが一番良い方法です

興味のある内容を考えてください。たとえば "blob のオブジェクト名からどのように 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_cha1()成功 時に 0 を返却します。これは Git のハックを 初めて行う人にとっては驚くべきことですが、エラーの種別毎に異なる負数を返却し、 成功時には 0 を返却するのは UNIX の昔からの伝統です。
  • get_sha1() 関数の戻り値である変数 sha1unsigned char * ですが、 実際は unsigned char[20] を指し示すことが想定されています。 この変数は指定されたコミットの 160-bit の SHA-1 を含んでいます。 SHA-1 が unsigned char * として渡される時はいつでも、 それがバイナリ表現であり、char * で渡される 16進の ASCII表現とは違う ことに注意してください。

これらの両方をソースを通して確認できます。

さて、ここが核心です:

        case 0:
                buf = read_object_with_reference(sha1, argv[1], &size, NULL);

これが blob (実際のところ、blob だけでなくすべてのオブジェクト)を読む方法です。 この関数 read_object_with_reference() がどのように動くかを知る為には その部分のソースコードを 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" を検索し、数行戻ると、 commit 18449ab0… にあるのがわかります。このオブジェクト名をコピーし コマンドラインにペーストします

$ git show 18449ab0

ビンゴ!

次の例:スクリプトをビルトイン化するために必要な作業を調べるには:

$ git log --no-merges --diff-filter=A builtin-*.c

ここまでに見てきたように、Git は Git 自身のソースを調べるときにも 最も役に立つツールなのです!

Chapter 11. GIT 用語集

代替オブジェクトDB(alternate object database)
代替メカニズム(alternates mechanism)により、リポジトリ は そのオブジェクトDBの一部を、他のオブジェクトDBから継承 することができます。この継承したものを "代替" オブジェクトDBといいます。
裸のリポジトリ(bare repository)
裸のリポジトリとは、通常 .git の拡張子を持つ ディレクトリ で、 リビジョン管理下にあるチェックアウトしたファイルをローカルに持たないディレクトリです。 通常 .git サブディレクトリ に隠れている git の管理ファイル全てが repository.git ディレクトリに直接存在し、 他のファイルは存在せず、チェックアウトもされていません。 通常、公開リポジトリを出版する人は、裸のリポジトリを作成します。
blob object
タイプ付けされていないオブジェクト, つまりファイルの中身です。
ブランチ(branch)
"ブランチ" はアクティブな開発ラインのことです。ブランチ上の最新のコミットは そのブランチの先端(tip)として参照されます。 ブランチの先端はブランチのヘッド(head)により参照され、 ブランチ上でさらに開発が進むと、先に進められます。 1つの git リポジトリは任意の数のブランチを追跡できますが、 作業ツリー はそれらブランチの1つ("カレント(current)" または"チェックアウト"ブランチ)と関連づけられ、HEADがその ブランチを指し示しています。
キャッシュ(cache)
索引(index) に対する廃語です。
チェーン(chain)
オブジェクトのリストのことで、そのリスト内の各オブジェクトが 後続する参照を含んでいるものを指します。(後続する参照とは、例えば コミットの場合、その親(parents)のことです)
チェンジセット(changeset)
"コミット" に対する BitKeeper/cvsps 側の用語です。 git は変更ではなく状態を格納する為、git で "チェンジセット" という用語を 使用するのは実際のところ意味がありません。
チェックアウト(checkout)
作業ツリーの全てもしくは一部を オブジェクトDB にある ツリーオブジェクト もしくはブロブ(blob)の内容で 更新する操作のことです。 もしも作業ツリー全体が新しい ブランチ を 指しているならば インデックスHEAD も更新します。
チェリーピック(cherry-picking)
SCMの用語では、"チェリーピック(cherry pick)" は 一連の変更(一般にはコミット)の一部を選択し、異なるコードベースの先頭に 新しい一連の変更としてそれらを記録することです。GITでは "git cherry-pick" コマンドによってコミットによる変更を抜き出し、 現在のブランチの先端に新しいコミットとして記録する操作のことです。
クリーン(clean)
作業ツリー がクリーンであるとは、 その作業ディレクトリが現在のヘッド(head)が参照する リビジョンと一致している場合を言います。 "dirty" も参照してください。
コミット(commit)

名詞:git の履歴内のある1点です。プロジェクトの全履歴は 相互に関連したコミットの集合により表現されています。git では"コミット"という言葉を 他のリビジョン管理システムが使用する "リビジョン" または "バージョン"と同じ意味で 使用することがあります。また、コミットオブジェクト の略称として使われることもあります。

動詞:プロジェクト状態の新しいスナップショットを git の履歴に格納する操作のことで、 索引(index)の現在の状態を現わす新しいコミットを作成し、 新しいコミットを指し示すように HEAD の位置を進めます。

コミットオブジェクト(commit object)
特定リビジョンの情報を含んだオブジェクトのことです。 コミットオブジェクトは親(parents)、コミットした人、著者、日付、 格納されているリビジョンの最上位のディレクトリと一致する ツリーオブジェクト(tree object)、などの情報が含まれています。
gitの中核(core git)
gitの基礎データ構造とユーティリティのこと。 ソースコード管理ツールとしての中核部分のことです。
DAG
有向非巡回グラフ。コミットオブジェクト は有向非巡回グラフ を形成します。何故ならコミットは(方向付けされた)親を持ち、 コミットオブジェクトのグラフは非巡回(つまり、始まりと終わりが 同じオブジェクトとなるような チェーン(chain) が存在しないからです)
遊離オブジェクト(dangling object)
到達不能なオブジェクトの内、他のどの到達不能オブジェクトからも 到達不能なオブジェクトのことです。つまり、リボジトリ内のどの参照と どのオブジェクトからも参照されていないオブジェクトのことです。
分離したHEAD (detached HEAD)
通常 HEAD はブランチ名を保管します。しかし、gitは特定のブランチの 先端(tip)ではない任意のコミットをチェックアウトすることもできます。 その状態のことを分離した(detached)といいます。
ディレクトリキャッシュ(dircache)
かなり時代遅れ な用語です。索引(index) を参照。
ディレクトリ(directory)
"ls" によって得られるリストのことです :-)
汚れている(dirty)
作業ツリー が "汚れている(dirty)" とは、 現在のブランチコミットされていない 変更が作業ツリー内に含まれていることをいいます。
エント(ent)
ギークが好んで使う "tree-ish" の同義語です。 詳しい説明は`http://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%B3%E3%83%88`を参照してください。 人々を混乱させない為にも、この言葉の使用はさけてください。
有害なマージ(evil merge)
有害なマージ(evil merge)とは、どのにも現れない 変更が行われているマージのことです。
早送り(fast forward)
早送り(fast forward) とは特殊なタイプのマージのことで、 マージにより得られる結果が作成済みの子孫と一致するマージのことをいいます。 このような場合、新しいマージコミットは作成されずに、 そのリビジョンを指すように更新されます。 早送り(fast forward)はリモートリポジトリ追跡ブランチ の場合に良く発生します。
フェッチ(fetch)
ブランチ(branch)を取得するとは、ブランチの リモートリポジトリからヘッドの参照(head ref) を取得し、ローカルのオブジェクトDBに存在しないオブジェクトを 探し出し、それらを取得することをいいます。git-fetch(1) も参照してください。
ファイルシステム(file system)
Linus Torvalds は元々はユーザ空間のファイルシステム、つまり、ファイルと ディレクトリを保持する構造基盤として git を設計していました。 その性質により、gitの効率性とスピードが保障されています。
gitアーカイブ(git archive)
リポジトリ の同義語です。(archユーザが使用します)
接ぎ木(grafts)
接ぎ木(grafts)とは、コミットに関する嘘の祖先情報を記録することで関連のない2つの開発ラインを つなぎ合わせることをいいます。この方法により、コミット作成時の記録とは違うコミットの の集合を、コミットのの集合であるかのように git に 行動させることができます。 この情報は .git/info/grafts ファイルにより設定します。
ハッシュ(hash)
git の文脈中では、オブジェクト名と同義です。
ヘッド(head)
ブランチの先端にあるコミットへの名前のついた参照 のことです。ヘッドは通常 $GIT_DIR/refs/heads/ の下に保管されますが、 例外として参照が圧縮(pack)されることもあります。(git-pack-refs(1) 参照)
HEAD
現在の ブランチ です。より詳しくは、 作業ツリーは通常、HEAD が参照する状態から導き出されます。 HEAD は通常、リポジトリ内の1つの ヘッド(head) を参照します。 例外として、分離したHEAD(detached HEAD)を使用している場合があり、 その場合は、任意のコミットを参照します。
head ref
ヘッド(head)と同義です。
フック(hook)
git コマンドのいくつかは実行中にオプションでスクリプトを呼び出すことができ、 開発者の機能追加やチェックの手助けをします。 通常、フックは事前検証を行い処理を中止する為や操作が完了した後に知らせる為に 用いられます。フックスクリプトは $GIT_DIR/hooks/ ディレクトリに保管されおり、 ファイル名から`.sample`を削除するだけで利用可能になります。 古いバージョンのgitではスクリプトを実行パーミッションを付与することで 利用可能になります。
索引(index)
索引はファイルとその状態の集合であり、その中身はオブジェクトとして格納されています。 作業ツリーの内容を保管しているバージョンです。 実は、索引は2つないし2つの作業ツリーのバージョンを含むことも可能で、 マージするときにそのような状態となります。
索引エントリ(index entry)
索引(index)内に格納された特定のファイル情報のことです。 索引エントリはマージが開始されているが 完了していない場合(すなわち、索引がファイルの複数の バージョンを含んでいる場合)には、マージが取り消されていることもあります。
master
デフォルトの開発ブランチのことです。 git リポジトリ を作成するといつでも "master" という名前のブランチが作成され、アクティブなブランチと なります。たいてい master ブランチにはローカルの開発を含みますが、 これは純粋に慣例によって行われていることであり必須ではありません。
マージ(merge)
動詞:他のブランチ(外部リポジトリのブランチも可) の内容を現在のブランチに届けることをいいます。 外部リポジトリからマージをする場合は、はじめにリモートブランチから 変更を取得(fetch)され、その結果が現在のブランチにマージされます。 この取得とマージ操作の組み合わせはpullと呼ばれています。 マージはそのブランチが分岐してから行われた変更を特定し、以降の変更をすべて 適用する作業を全自動で行います。コンフリクトが発生した場合は、 マージを完了する為に手作業の対処が必要になります。 +名詞:マージが早送り(fast forward)にならない場合、 マージは新しいコミットを作成し、 そのコミットはマージした結果を表現し、その親(parents)は マージされたブランチの先端となります。 このコミットは "merge commit" と呼ばれ、単に "merge" と呼ばれることもあります。
オブジェクト(object)
git のストレージ構成単位です。オブジェクトはその中身の SHA1 により 一意に特定されます。その結果、オブジェクトは不変となります。
オブジェクトDB(object database)
"object" の集合を保管します。個々のオブジェクト(object)は 自身のオブジェクト名によって特定されます。 オブジェクトは通常 $GIT_DIR/objects/ に存在します。
オブジェクトの識別子(object identifier)
オブジェクト名と同義。
オブジェクト名(object name)
オブジェクトのユニークな識別子です。 Secure Hash Algorithm 1 を使用して得られるオブジェクトのハッシュ(hash) であり、通常は40桁の16進数で表されます。
オブジェクトタイプ(object type)
識別子 "コミット", "ツリー", "タグ" または "ブロブ(blob)" の1つであり、 オブジェクト(object)のタイプを記述します。
タコ(octopus)
2つ以上のブランチマージすること。 または、知的な捕食動物を意味します。
origin
デフォルトの上流リポジトリのことを指します。 たいていのプロジェクトは少なくとも1つの追跡している上流プロジェクトがあります。 デフォルトで origin はその上流プロジェクトの追跡に使用されます。 上流の更新をすると、origin/name-of-upstream-branch の名前のリモートの 追跡ブランチに取得されます。 リモート追跡ブランチは git branch -r にて参照できます。
パック(pack)
オブジェクトの集合で、(ディスクスペースの節約とオブジェクトを効果的に伝達させる為に) 1つのファイルに圧縮されたものをいいます。
パック索引(pack index)
パック内のオブジェクトの識別子とその他情報の一覧のことで、 効果的にパックの中身にアクセスする為の手助けをします。
親(parent)
コミットオブジェクト内に含まれる開発ライン内の論理的な祖先 のことです(親が空の場合もあります)。
つるはし(pickaxe)
つるはし(pickaxe)という用語は指定したテキスト文字列を追加または削除 した変更を選択する手助けをする diffcore の動作オプションです。 —pickaxe-all オプションは指定したテキストラインを追加または削除した チェンジセットを全て表示する為に用います。 git-diff(1) 参照。
配管(plumbing)
gitの中核(core git) に対する可愛い呼び名です。
磁器(porcelain)
gitの中核(core git)に依存するプログラムと プログラムの一式に対する可愛い呼び名です。 gitの中核に対する高レベルなアクセスを提供します。 磁器(porcelain)は配管(plumbing)よりも SCMらしいインターフェースを提供します。
pull
ブランチを pull するとは、ブランチを取得し、 マージすることをいいます。git-pull(1) 参照。
プッシュ(push)
ブランチを push するとは、リモートリポジトリ からブランチのヘッド参照を取得し、 ローカル側のヘッド参照の直接の祖先であるかを確認し、 祖先である場合はローカルのヘッド参照から到達可能で リモートリポジトリに存在しない全てのオブジェクトをリモートの オブジェクトDBに設置し、 リモートのヘッド参照を更新します。 リモートのヘッドがローカルヘッドの祖先でない場合は、push は失敗します。
到達可能(reachable)
あるコミットの祖先全ては、そのコミットから "到達可能" であるといいます。 より一般的に、タグコミットツリーblobsが一方から他方につながっている場合、 そのオブジェクトがもう一方のオブジェクトから到達可能であるといいます。
リベース(rebase)
あるブランチの一連の変更をことなるベースに適用し、 ブランチのヘッドをその結果位置にリセットすること。
参照(ref)
SHA1 の 40バイトの16進数表現、または特定のオブジェクト を表す名前のことです。これらは $GIT_DIR/refs/ に格納されることがあります。
参照ログ(reflog)
参照ログ(reflog) は参照に関するローカルの "履歴" です。 言い換えると、このリポジトリの3つ前のリビジョンは何であったかや、 昨日のPM9:14時点ではこのリポジトリはどの状態にあったかなどを 教えてくれるものです。詳細は git-reflog(1) を参照してください。
参照仕様(refspec)
"参照仕様(refspec)" はリモートの参照とローカルの参照とのマッピングをする為に フェッチ(fetch)プッシュ(push) によって使用されます。 これらはコロン(:)で分割した <src>:<dst> の形式で、先頭に + 記号を付けることもあります。 例えば:git fetch $URL refs/heads/master:refs/heads/origin は "$URL にある master ブランチヘッド をつかみ、 それを自身のリポジトリ内の origin ブランチのヘッドに保管する" ことを意味します。 また git push $URL refs/heads/master:refs/heads/to-upstream は "自身のリポジトリ内の master ブランチのヘッドを $URL の to-upstream ブランチとして 発行する" ことを意味します。git-push(1) も参照してください。
リポジトリ(repository)
参照(refs)と参照から到達可能な全オブジェクトを 含んだオブジェクトDBの集合です。 1つ以上の 磁器(porcelains) のメタデータを含むこともあります。 リポジトリは代替メカニズムにより 他のリポジトリとオブジェクトDBを共有することもできます。
解決する(resolve)
自動 マージ に失敗した箇所を手作業で修復する 作業のことです。
リビジョン(revision)
オブジェクトDB 内に格納されたファイルとディレクトリ の特定の状態。これは コミットオブジェクト によって 参照されます。
巻き戻す(rewind)
開発の一部を破棄することです。例えば、ヘッド(head) を 以前の リビジョン に割り当てることです。
SCM
ソースコード管理(ツール)。
SHA1
オブジェクト名と同義。
浅いリポジトリ(shallow repository)
浅いリポジトリとは一部のコミットが リポジトリ内に存在していない不完全な履歴状態のリポジトリのことを言います。 (コミットオブジェクト内に親の情報が記録されていたとしても gitはこれらのコミットが親を持っていないふりをします) 浅いリポジトリは巨大プロジェクトにて最近の履歴にだけ興味があるような場合に 役に立ちます。git-clone(1)—depth オプションを付けることで 作成でき、git-fetch(1) により後から履歴をより深くすることもできます。
symref
シンボル参照:SHA1 の ID 自身を持つ代わりに、 ref: refs/some/thing の形式で参照先を持つ参照です。 参照されると再帰的に逆参照します。 HEAD は symref の典型例です。 シンボル参照は git-symbolic-ref(1) コマンドにより操作します。
タグ(tag)
タグ(tag)コミットオブジェクト を 指し示す 参照(ref) のことです。ヘッド(head) と異なり、 タグは コミット によって変更されることはありません。 タグ(タグオブジェクトではありません)は $GIT_DIR/refs/tags/ 内に 保管されます。git の タグは List tag とは無関係です (Lisp tagは、 git の資料内では オブジェクトタイプ と呼んでいます)。 タグはコミットの祖先の 繋がり(chain) 内の特定の位置に印を つけるために良く使用されます。
タグオブジェクト(tag object)
他のオブジェクトを指し示す 参照(ref) を含んでいる オブジェクト のことです。これは、コミットオブジェクト のようにメッセージを含むこともできます。また、署名(PGP) を含む "署名付きタグオブジェクト" と呼ばれるものもあります。
トピックブランチ (topic branch)
各開発ラインを識別する為に開発者が使用する通常の git ブランチ。 ブランチはとても容易で安価に利用できるため、明確なコンセプトや小さく付加的な 関連する変更を含んだ小さなブランチをいくつか作成することが望ましいです。 (訳注:日本語がおかしいので、訳をみなおす必要がある。以下に原文を載せます。 A regular git branch that is used by a developer to identify a conceptual line of development. Since branches are very easy and inexpensive, it is often desirable to have several small branches that each contain very well defined concepts or small incremental yet related changes.
追跡ブランチ (tracking branch)
他の リポジトリ からの変更を追跡する為に使用される 通常の git ブランチ。 追跡ブランチは直接の変更またはローカルのコミットは含むべきではありません。 追跡ブランチは通常、Pull 時に最も役に立つ 参照 です。: 参照仕様
ツリー (tree)
依存している blob と ツリーオブジェクトも含めた 作業ツリー または ツリーオブジェクト全体。 (つまり、作業ディレクトリの格納時の表現)。
ツリーオブジェクト (tree object)
blob または ツリーオブジェクトへの参照と、ファイル名とモードの一覧を含んだ オブジェクトツリーディレクトリ と等価。
ツリーっぽい (tree-ish)
コミットオブジェクトツリーオブジェクト、 あるいは タグオブジェクト を指し示している 参照。 (訳注:ツリーっぽくない参照は blob オブジェクトへの参照)
マージされていない索引 (unmerged index)
マージされていない 索引エントリ を含む 索引(index)
到達不可能なオブジェクト (unreachable object)
ブランチタグ、その他の参照から 到達可能 ではない オブジェクト
上流ブランチ(upstream branch)
マージを行う(もしくはリベースを行う) ディフォルトのブランチ のこと branch.<name>.remote と branch.<name>.merge によって設定される。 もしA'の上流ブランチが'origin/B'なら"'Aorigin/B を追跡している"と言う
作業ツリー (working tree)
実際にチェックアウトされたファイルのツリー。通常、作業ツリーは HEAD と まだコミットしていないローカルの変更とを 合わせた内容に等しいです。

Chapter 12. 付録A:Git Quick Reference

この章は主要コマンドのクイックサマリです; 詳細はこれまでの章の説明を参照してください。

新規リポジトリの作成

tar ボールから:

$ 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         # リポジトリ内のすべてのローカルブランチを表示
$ git checkout test  # 作業ディレクトリをブランチ "test" に切り替え
$ git branch new     # 現在の HEAD から始まる "new" ブランチを作成
$ git branch -d new  # "new" ブランチの削除

現在の HEAD (デフォルト)以外の場所から始まるブランチを作成するには、次のようにします:

$ git branch new test    # "test" ブランチのHEADから開始するブランチ "new" を作成
$ git branch new v2.6.15 # タグ v2.6.15 から開始するブランチ "new" を作成
$ git branch new HEAD^   # 最新のコミットの1つ前の位置から分岐するブランチを作成
$ git branch new HEAD^^  # 最新のコミットの2つ前の位置から分岐するブランチを作成
$ git branch new test~10 # ブランチ "test" の10個前の位置から分岐するブランチを作成

新しいブランチの作成と切り替えを同時に行うには:

$ git checkout -b new v2.6.15

複製(clone)元のリポジトリのブランチを更新しテストするには:

$ git fetch             # 更新
$ git branch -r         # リモートブランチの一覧を表示
  origin/master
  origin/next
  ...
$ git checkout -b masterwork origin/master

他のリポジトリ上にあるブランチを取得(fetch)し、 そのブランチに対して名前を付けるには:

$ 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                    # リモートリポジトリの一覧を表示
example
origin
$ git remote show example       # 詳細を表示
* remote example
  URL: git://example.com/project.git
  Tracked remote branches
    master
    next
    ...
$ git fetch example             # example のブランチを更新
$ git branch -r                 # すべてのリモートブランチの一覧を表示

履歴の探索

$ gitk                      # 履歴を視覚的にブラウズする
$ git log                   # すべてのコミットを一覧表示する
$ git log src/              # src/ 配下のファイルを変更している・・・
$ git log v2.6.15..v2.6.16  # v2.6.16 には含まれているが、v2.6.15 には含まれていない・・・
$ git log master..test      # test ブランチに含まれているが、master ブランチには含まれていない・・・
$ git log test..master      # master ブランチに含まれているが、test ブランチには含まれていない・・・
$ git log test...master     # 片方のブランチには含まれているが、両方のブランチには含まれていない・・・
$ git log -S'foo()'         # 差分に "foo()" を含んでいる・・・
$ git log --since="2 weeks ago"
$ git log -p                # パッチも表示する
$ git show                  # 最新のコミットを表示
$ git diff v2.6.15..v2.6.16 # 2つのタグ付されたバージョン間の差分
$ git diff v2.6.15..HEAD    # 現在の head との差分
$ git grep "foo()"          # 作業ディレクトリ内で "foo()" を検索する
$ git grep v2.6.15 "foo()"  # 過去のツリー内で "foo()" を検索する
$ git show v2.6.15:a.txt    # a.txt の過去のバージョンを表示する

回帰点を探す:

$ git bisect start
$ git bisect bad                # 現在のバージョンは bad
$ git bisect good v2.6.13-rc2   # 最新の good なリビジョンを知らせる
Bisecting: 675 revisions left to test after this
                                # ここからテストを行う
$ git bisect good               # このリビジョンが good の場合
$ git bisect bad                # このリビジョンが bad の場合
                                # 回帰点を見つけるまで続ける

変更する

誰の変更であるかを git に知らせる:

$ cat >>~/.gitconfig <<\EOF
[user]
        name = Your Name Comes Here
        email = [email protected]
EOF

次のコミットに含めるファイルの内容を指定し、 コミットする:

$ git add a.txt    # 更新したファイル
$ git add b.txt    # 新しいファイル
$ git rm c.txt     # 削除したファイル
$ git commit

コミットの準備と実行を一度に行う:

$ git commit d.txt # d.txt の最後の内容のみをコミット
$ git commit -a    # 管理対象の全ファイルの最新の状態をコミット

マージ

$ git merge test   # "test" ブランチを現在のブランチにマージ
$ git pull git://example.com/project.git master
                   # リモートブランチの変更を取得し、マージ
$ git pull . test  # git merge test と同じ

変更の共有

パッチをインポートし、エクスポートするには:

$ git format-patch origin..HEAD # HEAD に存在するが origin には存在しない
                                # 各コミットのパッチを整形する
$ git am mbox # メールボックス "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

Chapter 13. 付録 B: このマニュアルの覚え書きとTODOリスト

この仕事は作業中です。

基本要件:

  • UNIXコマンドラインの基礎知識があれば、git の前提知識がなくても 最初から最後まで順番に読み進められること。必要なときは、 その都度、他の前提知識が必要であることが述べられること。
  • 可能な時はいつでも、節の冒頭で方法を説明するタスクを明示的に説明するべきで、 言語に関する知識は必要以上には要求しない:例えば、"git am コマンド" ではなく、"パッチをプロジェクトにインポートする" とする。

全内容を読まなくても重要なトピックスの理解ができるよう、 各章の依存関係を明確にしたグラフの作成方法を考えるべきです。

Documentation/ ディレクトリをスキャンし、他の残項目を探す;特に:

howto に関するもの
technical/ の下のいくつか?
フック
linkgit:git[1] 内のコマンドの一覧

E-mail のアーカイブをスキャンし、他の残項目を探す。

man ページをスキャンし、to see if any assume more background than this manual provides.

Simplify beginning by suggesting disconnected head instead of temporary branch creation?

より良い例を追加する。全てのセクションに使用例を追加するのは 良いアイデアかもしれない;"高度な例" を章末のセクションに追加する?

用語集への相互参照を適切な場所に盛り込む。

shallow clone のドキュメントは? ドラフト版の 1.5.0 のリリースノート に幾らかの説明があります。

他のバージョン管理システムと作業する場合に関するセクションを追加する。 CVS, Subversion も含めて。リリースする tarball にそれらをインポートする。

gitweb に関するより詳しい説明?

配管スクリプトの使用と、スクリプトの書き方に関する章を追加。

交互の、clone -reference, など

リポジトリの破損からの復旧についてより詳しく。参照: http://marc.theaimsgroup.com/?l=git&m=117263864820799&w=2 http://marc.theaimsgroup.com/?l=git&m=117147855503798&w=2