Petr Baudis <pasky@suse.cz> writes:

> Dear diary, on Sun, Aug 14, 2005 at 09:57:13AM CEST, I got a letter
> where Junio C Hamano <junkio@cox.net> told me that...
>> Linus Torvalds <torvalds@osdl.org> writes:
>>
>> > Junioさん、「seen」ブランチから実際のブランチにパッチを移動させる
>> > 方法についてお話いただけますか?
>>
> 実際、これもStGITが意図していることにぴったり当てはまるのではないでしょうか?

まさに私の思っている通りの事です。私は Catalin が声を上げるのを(ちょっと)待っていました。 quilt を哲学的な祖先とする StGIT は、このような作業を行うために設計されているのです。

今回は、Gitのコアツールだけを使った、よりシンプルなものを作ってみました。

seenmaster よりも進んでいるコミットがひと握りほどあり、 私は seen で新しいものを最初に配置するという通常の習慣をバイパスして、いくつかのドキュメントを追加したいと思いました。 当初、コミットの家系図は以下のようになっていました:

                         *"seen" head
master --> #1 --> #2 --> #3

それで、私は master から始めて、たくさんの編集をして、そしてコミットしました:

$ git checkout master
$ cd Documentation; ed git.txt ...
$ cd ..; git add Documentation/*.txt
$ git commit -s

コミット後、家系図は以下のようになっています:

                          *"seen" head
master^ --> #1 --> #2 --> #3
      \
        \---> master

古い masterは 現在の master^(masterの最初の親)になりました。 新しい master のコミットは、ドキュメントの更新を保持しています。

いまや、私は「seen」ブランチへの対処をしなければなりません。

Linusがメンテナーであり、私がコントリビューターであったとき、「master」ブランチは「maintainer」ブランチであり、「seen」ブランチが「contributor」ブランチであるという状況でした。 あなた作業の少し前に「maintainer」ブランチの先端で始まり、その後沢山の作業を進めました。そして今、 maintainer にはまだあなたが持っていない他のコミットがいくつかあります。 そして、「git rebase」は、「seen」のようなブランチの維持を支援するという明確な目的で書かれました。 master を「seen」にマージして続行することも可能ですが、最終的に一部の変更を master ブランチにマージして戻す場合は、マージするのではなく seen をリベースする(つまり、あなたの変更を繰り越す)と、その後のあなたの操作が簡単になることがよくあります。 だから私は「git rebase」を実行しました:

$ git checkout seen
$ git rebase master seen

これは、masterブランチからフォークされた現在のブランチ(現在、私は「seen」ブランチにいることに注意してください)以降のすべてのコミットを選択し、これらの変更を転送することです。

master^ --> #1 --> #2 --> #3
      \                                  *"seen" head
        \---> master --> #1' --> #2' --> #3'

master^ と #1 の diff は master に適用され、コミット #1 から取得したコミット情報(ログ、作成者、日付)を使用して #1' コミットを作成するようにコミットされます。 その上で、 #2'#3' コミットは、 #2 と #3 のコミットから同様に行われます。

古い #3 は .git/refs/heads/ ファイルのいずれにも記録されなくなったため、これを実行した後、fsck-cache を実行すると、宙ぶらりんコミット(dangling commit)が発生します。これは正常です。 「seen」をテストした後、「git prune」を実行して、元の3つのコミットを取り除くことができます。

「git rebase」について話すにあたって、コアGitツールのみを使用してチェリーピッキングを行う方法について話しておきましょう。

以前の図に戻りましょう。でも、ラベルは以前と異なります。

あなたは個人開発者として、上流のリポジトリをクローンし、そのリポジトリでいくつかのコミットを行いました。

                           *your "master" head
upstream --> #1 --> #2 --> #3

あなたは、変更 #2 と #3 をアップストリームに組み込む必要がありますが、 あなたは #1 には更に改善が必要だと思いました。 したがって、あなたは電子メール送信用に #2 と #3 を準備します。

$ git format-patch master^^ master

これにより、 0001-XXXX.patch と 0002-XXXX.patch の2つのファイルが作成されます。 それらを "To: " であなたのプロジェクトメンテナと、"Cc: " であなたのメーリングリストに送信します。 ホストにこれに必要なperlモジュールがある場合は、提供されたスクリプト git-send-email を使用できますが、パッチ内の空白(whitespaces)が破損しない限り、あなたは通常のMUAを使用できます。

それからしばらく待つと、あなたは、アップストリームが他の変更とともにあなたの変更を取得したことが分かります。

 where                      *your "master" head
upstream --> #1 --> #2 --> #3
  used   \
 to be     \--> #A --> #2' --> #3' --> #B --> #C
                                              *upstream head

上の図の2つのコミット #2'#3' は、 #2 と #3 を含む電子メール送信に同一の変更を記録しますが、おそらく上流のメンテナによって追加された新しい sign-off 行と、間違いなく異なるコミッター名と家系情報(ancestry information)があり、それらは #2 と #3 のコミットとは異なるオブジェクトです。

あなたはアップストリームからフェッチしますが、マージはしません。

$ git fetch upstream

これにより、更新されたアップストリームヘッドは .git/FETCH_HEAD に残りますが、 .git/HEAD または .git/refs/heads/master には影響しません。 ここで「git rebase」を実行します。

$ git rebase FETCH_HEAD master

以前、私は、リベースはブランチからのすべてのコミットをアップストリームヘッドの上に適用すると言いました。 ええと、それは嘘です。「git rebase」はそれよりも少し賢く、 #2 と #3 を適用する必要がないので、 #1 のみを適用します。 コミットの家系図は以下のようになります:

 where                     *your old "master" head
upstream --> #1 --> #2 --> #3
  used   \                      your new "master" head*
 to be     \--> #A --> #2' --> #3' --> #B --> #C --> #1'
                                              *upstream
                                              head

繰り返しになりますが、「git prune」は、使用されなくなったコミット #1〜#3 を破棄し、新しい「master」ヘッド(#1' コミット)から開始し、続けます。

-jc