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