git rebase --ontoの動作を理解できません


169

次のgitコマンドの2つのブロックの動作が異なることに気づきましたが、その理由がわかりません。

私は1つと分岐AするBブランチとcommit

---COMMIT--- (A)
\
 --- (B)

最後にBブランチをリベースしたいA(そしてBブランチでコミットしたい)

---COMMIT--- (A)
         \
          --- (B)

私がそうすれば問題ありません:

checkout B
rebase A

しかし、私がするなら:

checkout B
rebase --onto B A

まったく機能しません。何も起こりません。2つの動作が異なる理由がわかりません。

Phpstorm git clientは2番目の構文を使用しているため、完全に壊れているように見えるので、この構文の問題を尋ねます。


回答:


412

tl; dr

あなたのケースBでのA使用git rebase --ontoに基づいてリベースする正しい構文は次のとおりです:

git checkout B
git rebase --onto A B^

または、または参照されている親であるコミットから開始することに基づいてリベースBABます。B^B~1

との違いに興味があるならgit rebase <branch>git rebase --onto <branch>読んでください。

クイック:git rebase

git rebase <branch>あなたが現在で参照される、チェックアウトしたブランチリベースうとしているHEAD上に、最新のは、それが到達可能であるコミットから<branch>しかしではないからHEAD
これは、リベースの最も一般的なケースであり、間違いなく、前もって計画を立てる必要が少ないケースです。

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                         \
              D---E (HEAD)                              D---E (HEAD)

この例では、FおよびGからは到達可能ですbranchがからは到達できないコミットですHEAD。言っgit rebase branchかかりますD最初の分岐点の後にコミットされており、その、それをリベース(すなわち、その親を変更から到達可能なコミット)最新の上branchではなく、からHEADです、G

正確:git rebase --ontoに2つの引数

git rebase --onto特定のコミットからリベースすることができます。これにより、リベースする対象と場所を正確に制御できます。これは、正確である必要があるシナリオ用です。

たとえば、から始めてHEAD正確にリベースする必要があるとしましょう。作業ブランチに取り込むことにのみ関心がありますが、同時に、互換性のない変更が含まれているため、保持したくありません。FEFD

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                     \
              D---E---H---I (HEAD)                  E---H---I (HEAD)

この場合、私達は言うでしょうgit rebase --onto F D。これの意味は:

HEAD親がのD上にいる到達可能なコミットをリベースしFます。

つまり、の親EからD変更Fます。次に、の構文はgit rebase --ontoですgit rebase --onto <newparent> <oldparent>

これが役立つもう1つのシナリオは、インタラクティブなリベースを行わずに、現在のブランチからいくつかのコミットをすばやく削除する場合です。

          Before                       After
    A---B---C---E---F (HEAD)        A---B---F (HEAD)

この例では、シーケンスから削除しCたりE、シーケンスから削除したりするには、と言うgit rebase --onto B Eか、古い親があった場所のHEAD上にリベースします。BE

外科医:git rebase --ontoと3つの引数

git rebase --onto精度の面でさらに一歩先を行くことができます。実際、任意の範囲のコミットを別のコミットの上にリベースすることができます。

次に例を示します。

          Before                                     After
    A---B---C---F---G (branch)                A---B---C---F---G (branch)
             \                                             \
              D---E---H---I (HEAD)                          E---H (HEAD)

この場合、現在のポイントを無視して、正確な範囲E---Hをの上にリベースします。我々は言ってそれを行うことができ、その手段は:FHEADgit rebase --onto F D H

親であるコミットの範囲リベースDまでHの上をF

構文git rebase --ontoコミットの範囲は、次になりgit rebase --onto <newparent> <oldparent> <until>。トリックはここで参照されるコミットすることを思い出している<until>されて含ま範囲で、新たなになりますHEADリベースが完了した後。


1
素敵な答え。一般的な場合のほんの少しの追加:<oldparent>範囲の2つの部分が異なるブランチ上にある場合、名前は壊れます。一般的には、「から到達可能なすべてのコミットを含める<until>が、から到達可能なすべてのコミットを除外し<oldparent>ます。」
musiKk 2015

50
git rebase --onto <newparent> <oldparent>私が見た--onto動作の最も良い説明です!
ronkot

4
ありがとう!私は--ontoオプションとちょっと苦労していましたが、これはそれを非常に明確にしました!以前は理解できなかった方法もわかりません:D優れた「チュートリアル」をありがとう:-)
grongor

3
この答えは素晴らしいですが、すべての可能性があるケースを網羅しているわけではないと感じています。最後の構文形式は、より微妙なタイプのリベースを表すためにも使用できます。Pro Git(第2版)の例では、Dは必ずしもHの祖先である必要はありません。代わりに、DとHは共通の祖先とコミットすることもできます。この場合、Gitは共通の祖先を見つけます。その祖先からHまでFに再生します
。–パスタリアン

1
これは役に立ちました。マニュアルページは引数をまったく説明していません。
a544jh

61

これはあなたが理解するために知る必要があるすべてです--onto

git rebase --onto <newparent> <oldparent>

コミットで親を切り替えますが、コミットのshaを提供せず、現在の(古い)親のshaのみを提供します。


4
短くて簡単。実際、リコミットしたいコミットの代わりにコミットを提供する必要があることに気づくと、最も長い時間がかかりました。
Antoniosss

1
詳細の重要な部分は、1つのコミットを多くのコミットの親にすることができるので、現在のブランチからoldparentの子を選択することですが、現在のブランチに制限すると、コミットは1つのコミットのみの親になることができます。つまり、親関係シップはブランチ上で一意ですが、ブランチを指定しない場合はそうである必要はありません。
Trismegistos

2
注:ブランチにいるか、ブランチ名を3番目のパラメーターとして追加する必要がありますgit rebase --onto <newparent> <oldparent> <feature-branch>
Jason Portnoy

1
この答えは驚くべきものであり、このスレッドで必要に応じてまっすぐに
John Culviner '18

13

簡単に言えば、

      Before rebase                             After rebase
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                         \   \
          D---E---H---I (HEAD)                      \   E'---H' (HEAD)
                                                     \
                                                      D---E---H---I

git rebase --onto F D H

これは同じです(--onto1つの引数を取るため):

git rebase D H --onto F

手段(D、H] F.お知らせの上に範囲が左排他的である範囲内でコミットをリベース。それはタイピングなどによりコミット第一を指定する方が簡単ですので、それは排他的だbranchようにするgit第一からコミット分岐見つけbranchすなわちDどのリードにH

OPケース

    o---o (A)
     \
      o (B)(HEAD)

git checkout B
git rebase --onto B A

単一のコマンドに変更できます。

git rebase --onto B A B

ここでエラーのように見えるのは、「上のBブランチBにつながるコミットを移動する」という意味の配置ですB。問題は、「一部のコミット」とは何かです。-iフラグを追加すると、それが指す単一のコミットであることがわかりますHEAD。コミットはすでに--ontoターゲットに適用されBているため、何も起こらないのでスキップされます。

このようにブランチ名が繰り返されている場合、コマンドは意味がありません。これは、コミットの範囲がすでにそのブランチにある一部のコミットであり、リベース中にすべてのコミットがスキップされるためです。

の詳細な説明と適切な使用法git rebase <upstream> <branch> --onto <newbase>

git rebase デフォルト。

git rebase master

次のいずれかに展開されます。

git rebase --onto master master HEAD
git rebase --onto master master current_branch

リベース後の自動チェックアウト。

標準的な方法で使用すると、次のようになります。

git checkout branch
git rebase master

リベースの後、最近リベースされたコミットにgit移動branchして実行することに気付かないでしょうgit checkout branchgit reflog履歴を参照)。2番目の引数がコミットハッシュである場合の興味深い点は、ブランチ名のリベースは引き続き機能しますが、移動するブランチがないため、移動したブランチにチェックアウトされる代わりに「デタッチされたヘッド」になってしまうことです。

一次分岐コミットを省略します。

master中には、--onto第一から取られるgit rebase引数。

                   git rebase master
                              /    \
         git rebase --onto master master

したがって、実際には、他のコミットまたはブランチにすることができます。この方法では、最新のコミットを取り、一次分岐コミットを残すことにより、リベースコミットの数を制限できます。

git rebase --onto master HEAD~
git rebase --onto master HEAD~ HEAD  # Expanded.

HEADtoが指す単一のコミットをリベースしmaster、最終的に「デタッチされたHEAD」になります。

明示的なチェックアウトを避けます。

デフォルトHEADまたはcurrent_branch引数は、現在の場所からコンテキストに基づいて取得されます。これが、ほとんどの人がリベースするブランチにチェックアウトする理由です。ただし、2番目のrebase引数が明示的に指定されている場合、暗黙的にそれを渡すためにrebaseの前にチェックアウトする必要はありません。

(branch) $ git rebase master
(branch) $ git rebase master branch  # Expanded.
(branch) $ git rebase master $(git rev-parse --abbrev-ref HEAD)  # Kind of what git does.

これは、任意の場所からコミットとブランチをリベースできることを意味します。したがって、リベース後の自動チェックアウトと一緒にリベースの前または後に、リベースされたブランチを個別にチェックアウトする必要はありません。

(master) $ git rebase master branch
(branch) $ # Rebased. Notice checkout.

8

簡単に言うgit rebase --ontoと、コミットの範囲を選択し、パラメーターとして指定されたコミットに基づいてリコミットします。

のマニュアルページを読み、git rebase「onto」を検索してください。例はとても良いです:

example of --onto option is to rebase part of a branch. If we have the following situation:

                                   H---I---J topicB
                                  /
                         E---F---G  topicA
                        /
           A---B---C---D  master

   then the command

       git rebase --onto master topicA topicB

   would result in:

                        H'--I'--J'  topicB
                       /
                       | E---F---G  topicA
                       |/
           A---B---C---D  master

この場合、あなたはからコミットをリベースするのgitに伝えるtopicAまでtopicBの上master


7

git rebaseとの違いをよりよく理解するにgit rebase --ontoは、両方のコマンドで可能な動作を知ることをお勧めします。git rebaseコミットを選択したブランチの上に移動できるようにします。ここのような:

git rebase master

結果は次のとおりです。

Before                              After
A---B---C---F---G (master)          A---B---C---F---G (master)
         \                                           \
          D---E (HEAD next-feature)                   D'---E' (HEAD next-feature)

git rebase --ontoより正確です。これにより、開始したい場所と終了したい場所の特定のコミットを選択できます。ここのような:

git rebase --onto F D

結果は次のとおりです。

Before                                    After
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                             \
          D---E---H---I (HEAD my-branch)                E'---H'---I' (HEAD my-branch)

詳細を取得するには、git rebase --onto overviewに関する自分の記事をチェックすることをお勧めします


@Makyen確かに、私は将来それを覚えておきます:)
womanonrails

そこで、我々は読むことができるgit rebase --onto F DようFとしてD'sの親の集合子たちではないが、?
Prihex

2

以下のためにonto、あなたは、2つの追加の分岐を必要とします。そのコマンドを使用するbranchBと、branchA別のブランチ(例:)に基づいたコミットを適用できますmaster。以下のサンプルbranchBはに基づいてbranchAおり、の変更を適用せずにbranchBon masterの変更を適用する必要がありbranchAます。

o---o (master)
     \
      o---o---o---o (branchA)
                   \
                    o---o (branchB)

コマンドを使用して:

checkout branchB
rebase --onto master branchA 

次のコミット階層になります。

      o'---o' (branchB)
     /
o---o (master)
     \
      o---o---o---o (branchA)

1
もう少し説明してください。マスターにリベースしたい場合、どうしてそれが現在のブランチになるのですか?もしそうするならrebase --onto branchA branchB、マスターブランチ全体をbranchAの頭に置きますか?
ポリメラーゼ

8
これはいけませんcheckout branchB: rebase --onto master branchAか?
goldenratio 2018

4
なぜこれが支持されたのですか?これはそれが言っていることをしません。
デ・ノボ

私が編集され、固定の答えを、人々は最初のブレークに自分のレポの枝を持っていないようにその直後 🙄...来て、コメントを読む
Kamafeather

0

git rebase --onto把握するのが難しい別のケースがあります:対称差分セレクター(3つのドット ' ...')の結果であるコミットにリベースする場合

Git 2.24(2019年第4四半期)は、そのケースをより適切に管理します。

参照414d924をコミットし4effc5bをコミットしc0efb4cをコミットし2b318aaをコミット(2019年8月27日)、および793ac7eをコミットし359ecebをコミットすることで(2019年8月25日)(デントン劉Denton-L
支援者:Eric Sunshine(sunshinecoJunio C Hamano(gitsterÆvarArnfjörðBjarmason(avarJohannes Schindelin(dscho
参照してください。6330209コミットc9efc21コミット(2019年8月27日)を、そして4336d36コミットにより(2019年08月25日)(ÆvarアインホルトBjarmason avar
支援者:エリックサンシャイン(sunshinecoジュニオCハマノ(gitsterÆvarArnfjörðBjarmason(avar、およびJohannes Schindelin(dscho
(合併によりJunio C浜野- gitster-640f9cdコミットし、2019年9月30日)

rebase--ontoより多くのケースで早送り

以前、次のグラフがあったとき、

A---B---C (master)
     \
      D (side)

git rebase --onto master... master side」を実行するとD、何があっても常にリベースされます。

この時点で、「読みダブルドット『との違いは何ですか..』とトリプルドット」...の範囲をコミットDIFF Gitの中には、「?

https://sphinx.mythic-beasts.com/~mark/git-diff-help.png

ここで: " master..."はを意味しますmaster...HEAD。つまり、BHEADはサイドHEAD(現在チェックアウトされています)です:あなたはにリベースしていBます。
何をリベースしていますか?どれがコミットいないマスターに、そしてから到達可能なsideブランチ:一つだけがその記述フィッティングコミットがありD...の上に既にありますB

繰り返しになりますが、Git 2.24より前では、そのようなa rebase --ontoD何があっても常にリベースされていました。

ただし、望ましい動作は、これが早送り可能であることをリベースが認識し、代わりにそれを行うことです。

それはrebase --onto B A何もしなかったOPに似ています。

can_fast_forwardこのケースを検出できるように検出を追加し、早送りを実行します。
最初に、ロジックを簡略化するgotosを使用するように関数を書き直します。
次に、

options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)

で条件が削除されたcmd_rebaseため、で代替品を再導入しました can_fast_forward
特に、のマージベースをチェックし、で失敗したケースupstreamhead修正しt3416ます。

t3416の簡略グラフは次のとおりです。

        F---G topic
       /
  A---B---C---D---E master

失敗したコマンドは

git rebase --onto master...topic F topic

以前は、Gitは1つのマージベース(C、の結果master...topic)があり、マージとオンは同じであるため、誤って1を返し、早送りできることを示していました。これにより、 ' ABCFG'が予期されていたときにリベースされたグラフが ' 'になりますABCG

A rebase --onto C F topicはの後の コミットを意味しFtopicHEAD から到達可能です。つまりGFそれ自体だけではありません。
この場合F、早送りはリベースされたブランチに含まれますが、これは誤りです。

追加のロジックを使用して、上流とヘッドのマージベースがであることを検出しFます。上ではないのでF、それはからのコミットの完全なセットをリベースしないことを意味しますmaster..topic
一部のコミットを除外しているため、早送りを実行できず、正しく0を返します。

' -f'を追加して、この変更の結果として失敗したテストケースに早送りを期待しておらず、リベースが強制されるようにします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.