development

git rebase --onto의 동작을 이해할 수 없습니다

big-blog 2020. 7. 2. 07:18
반응형

git rebase --onto의 동작을 이해할 수 없습니다


git 명령을 따르는 두 블록이 다른 동작을하며 그 이유를 이해하지 못합니다.

나는 하나와 분기 A하는 B지점과commit

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

가장 마지막에 B지점 을 리베이스하고 싶습니다 A(및 지점에 커밋을 가짐 B)

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

내가해도 문제가 없습니다.

checkout B
rebase A

그러나 내가하면 :

checkout B
rebase --onto B A

전혀 작동하지 않으며 아무 일도 일어나지 않습니다. 두 동작이 다른 이유를 이해하지 못합니다.

Phpstorm git 클라이언트는 두 번째 구문을 사용하므로 완전히 깨진 것처럼 보이 므로이 구문 문제를 묻습니다.


tl; dr

귀하의 경우 BA사용 하는 것 외에 rebase하는 올바른 구문 git rebase --onto은 다음과 같습니다.

git checkout B
git rebase --onto A B^

또는 REBASE B의 상단 A으로부터 시작하는 그의 부모 커밋B 로 참조 B^하거나 B~1.

당신 git rebase <branch>의 차이점에 관심이 있다면 git rebase --onto <branch>.

빠른 : 자식 리베이스

git rebase <branch>에 의해 참조 현재 체크 아웃 한 분기를, 리베이스 것입니다 HEAD의 상단에, 최신가에 도달 할 수 커밋 에서 <branch>하지만, 하지 에서 HEAD.
이것은 가장 일반적인 rebasing의 경우이며 아마도 계획을 덜 필요로하는 경우입니다.

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

이 예에서 FG에서 도달 할 수 branch있지만에서 도달 할 수 없는 커밋입니다 HEAD. 말하는 git rebase branch소요됩니다 D(가) 먼저 분기 시점 이후 커밋입니다, 그리고 그것을 리베이스 (즉, 부모를 변경 에서 연결할 최신의 상단에) 커밋 branch아니라에서 HEAD입니다, G.

정확한 : git rebase --onto 2 arguments

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>.

이것이 유용한 또 다른 시나리오는 대화식 리베이스 를 수행하지 않고 현재 분기에서 일부 커밋을 신속하게 제거하려는 경우입니다 .

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

이 예에서 순서대로 제거하기 CE순서에서 당신은 말할 것입니다 git rebase --onto B E, 또는 리베이스 HEAD위에 B오래된 부모가 어디 E.

외과 의사 : git rebase --onto 3 arguments

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위에을 F위치를 무시하고, HEAD현재 가리키고 있습니다. 우리는 다음과 같이 말함으로써 그렇게 할 수 있습니다 git rebase --onto F D H.

부모 커밋의 범위 리베이스 D까지 H의 위에을 F.

그러면 커밋 범위git rebase --onto 가있는 구문 이됩니다 . 트릭은 여기에 의해 참조 커밋 것을 기억한다 됩니다 포함 범위와 새로운 될 것 REBASE가 완료된 후.git rebase --onto <newparent> <oldparent> <until><until>HEAD


이것은 당신이 알아야 할 모든 것입니다 --onto:

git rebase --onto <newparent> <oldparent>

커밋에서 부모를 전환하고 있지만 커밋의 샤를 제공하지 않고 현재 (오래된) 부모의 샤만 제공합니다.


간단히, 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에게 커밋을에서 rebase로 리베이스하도록 topicA지시 topicB합니다 master.


다음과 같이 주어진다 :

      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

Which is the same as (because --onto takes one argument):

git rebase D H --onto F

Means rebase commits in range (D, H] on top of F. Notice the range is left-hand exclusive. It's exclusive because it's easier to specify 1st commit by typing e.g. branch to let git find the 1st diverged commit from branch i.e. D which leads to H.

OP case

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

git checkout B
git rebase --onto B A

Can be changed to single command:

git rebase --onto B A B

What looks like error here is placement of B which means "move some commits which lead to branch B on top of B". The questions is what "some commits" are. If you add -i flag you will see it is single commit pointed by HEAD. The commit is skipped because it is already applied to --onto target B and so nothing happens.

The command is nonsense in any case where branch name is repeated like that. This is because the range of commits will be some commits which are already in that branch and during rebase all of them will be skipped.

Further explanation and applicable usage of git rebase <upstream> <branch> --onto <newbase>.

git rebase defaults.

git rebase master

Expands to either :

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

Automatic checkout after rebase.

When used in standard way, like:

git checkout branch
git rebase master

You won't notice that after rebase git moves branch to most recently rebased commit and does git checkout branch (see git reflog history). What is interesting when 2nd argument is commit hash instead branch name rebase still works but there is no branch to move so you end up in "detached HEAD" instead being checked out to moved branch.

Omit primary diverged commits.

The master in --onto is taken from 1st git rebase argument.

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

So practicaly it can be any other commit or branch. This way you can limit number of rebase commits by taking the latest ones and leaving primary diverged commits.

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

Will rebase single commit pointed by HEAD to master and end up in "detached HEAD".

Avoid explicit checkouts.

The default HEAD or current_branch argument is contextually taken from place you're in. This is why most people checkout to branch which they want to rebase. But when 2nd rebase argument is given explicitly you don't have to checkout before rebase to pass it in implicit way.

(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.

This means you can rebase commits and branches from any place. So together with Automatic checkout after rebase. you don't have to separately checkout rebased branch before or after rebase.

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

For onto you need two additional branches. With that command you can apply commits from branchB that are based on branchA onto another branch e.g. master. In the sample below branchB is based on branchA and you want to apply the changes of branchB on master without applying the changes of branchA.

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

by using the commands:

checkout master
rebase --onto branchA branchB

you will get following commit hierarchy.

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

There is another case where git rebase --onto is hard to grasp: when you rebase onto a commit resulting of a symmetric difference selector (the three dots '...')

Git 2.24 (Q4 2019) does a better job of managing that case:

See commit 414d924, commit 4effc5b, commit c0efb4c, commit 2b318aa (27 Aug 2019), and commit 793ac7e, commit 359eceb (25 Aug 2019) by Denton Liu (Denton-L).
Helped-by: Eric Sunshine (sunshineco), Junio C Hamano (gitster), Ævar Arnfjörð Bjarmason (avar), and Johannes Schindelin (dscho).
See commit 6330209, commit c9efc21 (27 Aug 2019), and commit 4336d36 (25 Aug 2019) by Ævar Arnfjörð Bjarmason (avar).
Helped-by: Eric Sunshine (sunshineco), Junio C Hamano (gitster), Ævar Arnfjörð Bjarmason (avar), and Johannes Schindelin (dscho).
(Merged by Junio C Hamano -- gitster -- in commit 640f9cd, 30 Sep 2019)

rebase: fast-forward --onto in more cases

Before, when we had the following graph,

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

running 'git rebase --onto master... master side' would result in D being always rebased, no matter what.

At this point, read "What are the differences between double-dot '..' and triple-dot "..." in Git diff commit ranges?"

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

Here: "master... " refers to master...HEAD, which is B: HEAD is side HEAD (currently checked out): you are rebasing onto B.
What are you rebasing? Any commit not in master, and reachable from side branch: there is only one commit fitting that description: D... which is already on top of B!

Again, before Git 2.24, such a rebase --onto would result in D being always rebased, no matter what.

However, the desired behavior is that rebase should notice that this is fast-forwardable and do that instead.

That is akin to the rebase --onto B A of the OP, which did nothing.

Add detection to can_fast_forward so that this case can be detected and a fast-forward will be performed.
First of all, rewrite the function to use gotos which simplifies the logic.
Next, since the

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

conditions were removed in cmd_rebase, we reintroduce a substitute in can_fast_forward.
In particular, checking the merge bases of upstream and head fixes a failing case in t3416.

The abbreviated graph for t3416 is as follows:

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

and the failing command was

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

Before, Git would see that there was one merge base (C, result of master...topic), and the merge and onto were the same so it would incorrectly return 1, indicating that we could fast-forward. This would cause the rebased graph to be 'ABCFG' when we were expecting 'ABCG'.

A rebase --onto C F topic means any commit after F, reachable by topic HEAD: that is G only, not F itself.
Fast-forwarding in this case would include F in the rebased branch, which is wrong.

With the additional logic, we detect that upstream and head's merge base is F. Since onto isn't F, it means we're not rebasing the full set of commits from master..topic.
Since we're excluding some commits, a fast-forward cannot be performed and so we correctly return 0.

Add '-f' to test cases that failed as a result of this change because they were not expecting a fast-forward so that a rebase is forced.

참고URL : https://stackoverflow.com/questions/29914052/i-cant-understand-the-behaviour-of-git-rebase-onto

반응형