通常のgit rebaseと同様に、git withは、--preserve-merges
最初にコミットグラフの一部で行われたコミットのリストを識別し、次にそれらのコミットを別の部分の上に再生します。--preserve-merges
どのコミットが再生用に選択されるか、およびそのコミットがマージコミットでどのように機能するかを考慮した違い。
通常のリベースとマージ保持リベースの主な違いについてより明確にするために:
- 通常のリベースはマージコミットを完全に無視するのに対し、マージ保持リベースは(一部の)マージコミットを再生する用意があります。
- マージコミットを再生する用意があるため、マージ保持リベースは、マージコミットを再生することの意味を定義し、余分なしわに対処する必要があります。
- 概念的に最も興味深い部分は、おそらく新しいコミットのマージの親がどうあるべきかを選択することです。
- マージコミットを再生するには、特定のコミット(
git checkout <desired first parent>
)を明示的にチェックアウトする必要もありますが、通常のリベースではそれを心配する必要はありません。
- マージ保持リベースは、再生のためにコミットのより浅いセットを考慮します。
- 特に、最新のマージベース以降に行われたコミットの再生のみが考慮されます。つまり、2つのブランチが分岐した最新の時間ですが、通常のリベースは、2つのブランチが分岐した最初のコミットに戻ってコミットを再生する場合があります。
- 暫定的で不明確になるために、これは最終的に、マージコミットにすでに「組み込まれている」「古いコミット」の再生を排除するための手段だと思います。
最初に--preserve-merges
、リベースが行うことを「十分に正確に」説明しようと試み、次にいくつかの例を示します。もちろん、それがより有用であると思われる場合は、例から始めることができます。
「簡単」のアルゴリズム
本当に雑草に入りたい場合は、gitソースをダウンロードしてファイルを調べてくださいgit-rebase--interactive.sh
。(RebaseはGitのCコアの一部ではなく、bashで記述されています。また、舞台裏では、「インタラクティブなrebase」とコードを共有しています。)
しかし、ここで私はそれの本質であると思うものをスケッチします。考えることの数を減らすために、私はいくつかの自由を取りました。(たとえば、計算が行われる正確な順序を100%の精度でキャプチャしようとせず、一部のそれほど中心的ではないトピック、たとえば、ブランチ間ですでに厳選されているコミットについてどうするかを無視します)。
最初に、マージを保持しないリベースはかなり単純であることに注意してください。それは多かれ少なかれです:
Find all commits on B but not on A ("git log A..B")
Reset B to A ("git reset --hard A")
Replay all those commits onto B one at a time in order.
リベース--preserve-merges
は比較的複雑です。これは、非常に重要と思われるものを失うことなく作成できたのと同じくらい簡単です。
Find the commits to replay:
First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s))
This (these) merge base(s) will serve as a root/boundary for the rebase.
In particular, we'll take its (their) descendants and replay them on top of new parents
Now we can define C, the set of commits to replay. In particular, it's those commits:
1) reachable from B but not A (as in a normal rebase), and ALSO
2) descendants of the merge base(s)
If we ignore cherry-picks and other cleverness preserve-merges does, it's more or less:
git log A..B --not $(git merge-base --all A B)
Replay the commits:
Create a branch B_new, on which to replay our commits.
Switch to B_new (i.e. "git checkout B_new")
Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new:
If it's a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c")
Otherwise it's a merge commit, and we'll construct an "equivalent" merge commit c':
To create a merge commit, its parents must exist and we must know what they are.
So first, figure out which parents to use for c', by reference to the parents of c:
For each parent p_i in parents_of(c):
If p_i is one of the merge bases mentioned above:
# p_i is one of the "boundary commits" that we no longer want to use as parents
For the new commit's ith parent (p_i'), use the HEAD of B_new.
Else if p_i is one of the commits being rewritten (i.e. if p_i is in R):
# Note: Because we're moving parents-before-children, a rewritten version
# of p_i must already exist. So reuse it:
For the new commit's ith parent (p_i'), use the rewritten version of p_i.
Otherwise:
# p_i is one of the commits that's *not* slated for rewrite. So don't rewrite it
For the new commit's ith parent (p_i'), use p_i, i.e. the old commit's ith parent.
Second, actually create the new commit c':
Go to p_1'. (i.e. "git checkout p_1'", p_1' being the "first parent" we want for our new commit)
Merge in the other parent(s):
For a typical two-parent merge, it's just "git merge p_2'".
For an octopus merge, it's "git merge p_2' p_3' p_4' ...".
Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it's not already there
Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")
--onto C
引数を使用したリベースは非常によく似ています。Bのヘッドでコミット再生を開始するのではなく、代わりにCのヘッドでコミット再生を開始します。(そしてB_newの代わりにC_newを使用してください。)
例1
たとえば、コミットグラフを取る
B---C <-- master
/
A-------D------E----m----H <-- topic
\ /
F-------G
mは、親EおよびGとのマージコミットです。
通常のマージを維持しないリベースを使用して、マスター(C)の上にトピック(H)をリベースするとします。(たとえば、checkout topic; rebase master)。その場合、gitは次のコミットを選択して再生します。
そして、次のようにコミットグラフを更新します:
B---C <-- master
/ \
A D'---E'---F'---G'---H' <-- topic
(D 'はDの再生された同等物などです。)
マージコミットmは再生用に選択されていないことに注意してください。
代わりに--preserve-merges
、Cの上にHのリベースを実行した場合(たとえば、checkout topic; rebase --preserve-merges master)。この新しいケースでは、gitは次のコミットを選択して再生します。
- Dを選ぶ
- Eを選択
- Fを選択(「サブトピック」ブランチのDに)
- Gを選択(「サブトピック」ブランチのFに)
- マージブランチ「サブトピック」をトピックに選択
- Hを選択
今度はm がリプレイに選ばれました。また、マージの親EとGは、マージコミットmの前に含める対象として選択されたことにも注意してください。
結果のコミットグラフは次のとおりです。
B---C <-- master
/ \
A D'-----E'----m'----H' <-- topic
\ /
F'-------G'
繰り返しますが、D 'はDのチェリーピック(つまり、再作成)バージョンです。E'も同様です。マスター以外のすべてのコミットがリプレイされました。EとG(mのマージ親)の両方がE 'とG'として再作成され、m 'の親として機能します(リベース後も、ツリー履歴は同じままです)。
例2
通常のリベースとは異なり、マージ保持リベースは上流ヘッドの複数の子を作成できます。
たとえば、次のことを考慮してください。
B---C <-- master
/
A-------D------E---m----H <-- topic
\ |
------- F-----G--/
C(マスター)の上にH(トピック)をリベースする場合、リベースのために選択されるコミットは次のとおりです。
- Dを選ぶ
- Eを選択
- Fを選ぶ
- Gを選択
- ピックm
- Hを選択
結果は次のようになります:
B---C <-- master
/ | \
A | D'----E'---m'----H' <-- topic
\ |
F'----G'---/
例3
上記の例では、マージコミットとその2つの親の両方が、元のマージコミットにある元の親ではなく、再生されたコミットです。ただし、他のリベースでは、再生されたマージコミットは、マージ前にすでにコミットグラフにあった親で終了する可能性があります。
たとえば、次のことを考慮してください。
B--C---D <-- master
/ \
A---E--m------F <-- topic
トピックをマスターにリベースする場合(マージを保持)、再生へのコミットは次のようになります
書き換えられたコミットグラフは次のようになります。
B--C--D <-- master
/ \
A-----E---m'--F'; <-- topic
ここで、再生されたマージコミットm 'は、コミットグラフに既存の親、つまりD(マスターのHEAD)とE(元のマージコミットmの親の1つ)を取得します。
実施例4
マージ保持リベースは、特定の「空のコミット」の場合に混乱する可能性があります。少なくともこれは、一部の古いバージョンのgit(たとえば、1.7.8)にのみ当てはまります。
このコミットグラフを見てください:
A--------B-----C-----m2---D <-- master
\ \ /
E--- F--\--G----/
\ \
---m1--H <--topic
コミットm1とm2の両方にBとFからのすべての変更が組み込まれている必要があることに注意してください。
git rebase --preserve-merges
H(トピック)をD(マスター)に変換しようとすると、次のコミットが再生用に選択されます。
m1で統合された変更(B、F)はすでにDに組み込まれていることに注意してください(m2はBとFの子をマージするため、これらの変更はすでにm2に組み込まれているはずです)。 Dは、おそらく何もしないか、空のコミット(つまり、連続するリビジョン間の差分が空のコミット)を作成する必要があります。
ただし、代わりに、gitはDの上でm1を再生する試みを拒否する場合があります。次のようなエラーが発生する可能性があります。
error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed
gitにフラグを渡すのを忘れたように見えますが、根本的な問題は、gitが空のコミットの作成を嫌うことです。
git --rebase-merges
最終的に古いが置き換えられgit --preserve-merges
ます。以下の私の回答を