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

Table of Contents

一連の完全なパッチの作成
1つのコミットを再編集する
一連のパッチの並び替えや選択
他のツール
履歴の書き換えによって生じる問題
マージコミットの分割が1本線の履歴の分割よりも困難となる理由

通常コミットはプロジェクトに追加されるのみで、削除したり置き換えられる ことはありません。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