Não consigo entender o comportamento do git rebase --onto

169

Percebi que os dois blocos dos seguintes comandos git têm comportamentos diferentes e não entendo o porquê.

Eu tenho um Ae um Bramo que diverge com umcommit

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

Quero refazer a Bramificação no último A(e ter o commit na Bramificação)

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

Não tem problema se eu fizer:

checkout B
rebase A

Mas se eu fizer:

checkout B
rebase --onto B A

Não funciona, nada acontece. Não entendo por que os dois comportamentos são diferentes.

O cliente phpstorm git usa a segunda sintaxe e, portanto, parece-me completamente quebrado, é por isso que peço esse problema de sintaxe.

Xmanoux
fonte

Respostas:

412

tl; dr

A sintaxe correta a ser reformulada Bapós o Auso git rebase --ontono seu caso é:

git checkout B
git rebase --onto A B^

ou rebase Bapós Ainiciar a confirmação que é o pai doB referenciado com B^ou B~1.

Se você estiver interessado na diferença entre git rebase <branch>e git rebase --onto <branch>continue lendo.

O Quick: git rebase

git rebase <branch>vai rebase o ramo que você tem atualmente check-out, referenciado por HEAD, no topo do mais recente commit que está acessível a partir <branch>, mas não a partir HEAD.
Este é o caso mais comum de rebaseamento e, sem dúvida, o que exige menos planejamento antecipado.

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

Neste exemplo, Fe Gsão confirmações acessíveis, branchmas não acessíveis HEAD. Dizendo git rebase branchlevará D, que é o primeiro comprometer depois do ponto de ramificação e rebase-lo (ou seja, alterar o seu pai ) no topo da mais recente commit acessível a partir branchmas não do HEADque é G.

The Precise: git rebase --onto com 2 argumentos

git rebase --ontopermite que você renasça a partir de uma confirmação específica . Concede a você controle exato sobre o que está sendo reformulado e onde. Isso é para cenários em que você precisa ser preciso.

Por exemplo, vamos imaginar que precisamos nos refazer HEADprecisamente antes de Fcomeçar E. Estamos interessados ​​apenas em entrar Fem nosso ramo de trabalho e, ao mesmo tempo, não queremos mantê- Dlo porque ele contém algumas alterações incompatíveis.

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

Nesse caso, diríamos git rebase --onto F D. Isso significa:

Rebase a confirmação acessível de HEADquem o pai está Dno topo F.

Em outras palavras, altere o pai de Ede Dpara F. A sintaxe de git rebase --ontoé então git rebase --onto <newparent> <oldparent>.

Outro cenário em que isso é útil é quando você deseja remover rapidamente algumas confirmações da ramificação atual sem ter que fazer uma nova análise interativa :

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

Neste exemplo, a fim de remover Ce Eda sequência, você diria git rebase --onto B Eou rebase, HEADalém de Bonde estava o pai antigo E.

The Surgeon: git rebase --onto com 3 argumentos

git rebase --ontopode dar um passo adiante em termos de precisão. De fato, ele permite que você refaça um intervalo arbitrário de confirmações sobre outras.

Aqui está um exemplo:

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

Nesse caso, queremos refazer o intervalo exato E---Hem cima F, ignorando para onde HEADestá apontando atualmente. Podemos fazer isso dizendo git rebase --onto F D H, o que significa:

Rebase a faixa de commits cujo pai é Daté Hem cima F.

A sintaxe de git rebase --ontocom um intervalo de confirmações se torna git rebase --onto <newparent> <oldparent> <until>. O truque aqui é lembrar que o commit referenciado por <until>está incluído no intervalo e se tornará o novo HEADapós a conclusão da recuperação.

Enrico Campidoglio
fonte
1
Boa resposta. Apenas uma pequena adição ao caso geral: o <oldparent>nome será quebrado se as duas partes do intervalo estiverem em ramificações diferentes. Geralmente é: "Inclua todas as confirmações acessíveis, <until>mas exclua todas as confirmações acessíveis <oldparent>".
precisa saber é
50
git rebase --onto <newparent> <oldparent>é a melhor explicação para o comportamento --no que eu já vi!
Ronkot
4
Obrigado! Eu estava meio que lutando com a --ontoopção, mas isso deixou tudo claro! Eu nem entendo como eu não poderia ter entendido isso antes: D Obrigado por excelente "tutorial" :-)
grongor
3
Embora essa resposta seja excelente, acho que não abrange todos os casos possíveis. A última forma de sintaxe também pode ser usada para expressar um tipo mais sutil de rebase. Saindo do exemplo no Pro Git (2ª Ed.), D não precisa necessariamente ser um ancestral de H. Em vez disso, D e H também podem ser confirmados com um ancestral comum - nesse caso, o Git descobrirá seu ancestral comum e repetir a partir desse ancestral a H em F.
Pastafarian
1
Isso foi útil. A página do manual não explica os argumentos.
precisa saber é o seguinte
61

É tudo o que você precisa saber para entender --onto:

git rebase --onto <newparent> <oldparent>

Você está alternando um pai em um commit, mas não está fornecendo o sha do commit, apenas o sha do pai atual (antigo).

nunca
fonte
4
Curto e fácil. Na verdade, perceber que eu tenho que fornecer a confirmação pai daquela que eu quero recuperar em vez dessa confirmação levou mais tempo.
Antoniossss
1
Um detalhe importante é que você escolhe um filho de um pai antigo da ramificação atual porque uma confirmação pode ser pai de muitas confirmações, mas quando você se restringe à ramificação atual, a confirmação pode ser pai apenas de uma confirmação. Em outras palavras, a relação de parentesco é única na ramificação, mas não precisa ser, se você não especificar a ramificação.
Trismegistos
2
Para NOTA: Você precisa estar no ramo, ou adicionar o nome da ramificação como o 3º parâmetro git rebase --onto <newParent> <oldparent> <feature-branch>
Jason Portnoy
1
Esta resposta é incrível, direto ao ponto, conforme necessário neste tópico
John Culviner
13

Em breve, dado:

      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

Qual é o mesmo que (porque --ontoleva um argumento):

git rebase D H --onto F

Significa rebase commit no intervalo (D, H] em cima de F. Observe que o range é exclusivo à esquerda. É exclusivo porque é mais fácil especificar o 1º commit digitando, por exemplo, branchpara deixar gitencontrar o 1º commit divergente, branchou seja, o Dque leva a H.

Caso OP

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

git checkout B
git rebase --onto B A

Pode ser alterado para um único comando:

git rebase --onto B A B

O que parece erro aqui é o posicionamento, Bque significa "mover alguns commits que levam a ramificação Bpor cima B". A questão é o que "alguns commits" são. Se você adicionar -isinalizador, verá que é um único commit apontado por HEAD. A confirmação foi ignorada porque já foi aplicada ao --ontodestino Be, portanto, nada acontece.

O comando não faz sentido, em qualquer caso em que o nome do ramo seja repetido assim. Isso ocorre porque o intervalo de confirmações será de algumas confirmações que já estão nessa ramificação e, durante a reformulação, todas elas serão ignoradas.

Mais explicações e uso aplicável de git rebase <upstream> <branch> --onto <newbase>.

git rebase padrões.

git rebase master

Expande para:

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

Check-out automático após o rebase.

Quando usado de maneira padrão, como:

git checkout branch
git rebase master

Você não notará que, após o rebase, gitpassar branchpara o commit rebased mais recente e o faz git checkout branch(consulte o git refloghistórico). O que é interessante quando o segundo argumento é o hash de confirmação, em vez disso, a rebase do nome da ramificação ainda funciona, mas como não há ramificação a ser movida, você acaba em "HEAD desanexado" em vez de fazer check-out na ramificação movida.

Omita confirmações divergentes primárias.

O masterin --ontoé retirado do 1º git rebaseargumento.

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

Tão prático que pode ser qualquer outro commit ou ramo. Dessa forma, você pode limitar o número de confirmações de rebase, pegando as mais recentes e deixando as confirmações divergentes primárias.

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

Vai rebase única confirmação apontado por HEADque mastere acabam em "CABEÇA destacada".

Evite checkouts explícitos.

O padrão HEADou current_branchargumento é contextualmente retirado do local em que você está. É por isso que a maioria das pessoas faz checkout para ramificar as quais deseja refazer. Mas quando o segundo argumento de rebase é fornecido explicitamente, você não precisa fazer check-out antes de rebase para passá-lo de maneira implícita.

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

Isso significa que você pode recuperar as confirmações e ramificações de qualquer lugar. Então, juntamente com o checkout automático após o rebase.você não precisa fazer check-out separado da ramificação rebatida antes ou depois da rebase.

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

Simplificando, git rebase --onto seleciona um intervalo de confirmações e as refaz na confirmação fornecida como parâmetro.

Leia as páginas de manual git rebase, procure por "no". Os exemplos são muito bons:

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

Nesse caso, você diz ao git para refazer as confirmações de topicApara o topicBtopo master.

Gauthier
fonte
8

Para entender melhor a diferença entre git rebasee git rebase --ontoé bom saber quais são os comportamentos possíveis para os dois comandos. git rebasenos permitem mover nossos commits no topo do ramo selecionado. Como aqui:

git rebase master

e o resultado é:

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é mais preciso. Ele nos permite escolher o commit específico onde queremos começar e também onde queremos terminar. Como aqui:

git rebase --onto F D

e o resultado é:

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)

Para obter mais detalhes, recomendo que você verifique meu próprio artigo sobre o git rebase - resumo geral

womanonrails
fonte
@ Makyen Claro, eu vou manter isso em mente no futuro :)
womanonrails 21/04
Então, podemos ler git rebase --onto F Dcomo filho fixo do pai de D como F , não podemos?
Prihex 07/07
2

Para ontovocê precisar de dois ramos adicionais. Com esse comando, você pode aplicar confirmações branchBbaseadas em branchAoutra ramificação, por exemplo master. Na amostra abaixo, branchBé baseado branchAe você deseja aplicar as alterações de branchBon mastersem aplicar as alterações de branchA.

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

usando os comandos:

checkout branchB
rebase --onto master branchA 

você seguirá a hierarquia de confirmação.

      o'---o' (branchB)
     /
o---o (master)
     \
      o---o---o---o (branchA)
Michael Mairegger
fonte
1
Você pode explicar um pouco mais, se quisermos nos refazer no master, como é que ele se torna o ramo atual? Se o fizer rebase --onto branchA branchB, isso colocaria todo o ramo principal na cabeça do ramoA?
Polymerase
8
não deveria ser assim checkout branchB: rebase --onto master branchA?
### goldenratio #
4
Por que isso foi votado? isso não faz o que diz que faz.
De Novo
Eu editei e corrigi a resposta, para que as pessoas não precisem primeiro interromper seus ramos de recompra e logo depois venham ler os comentários ... 🙄
Kamafeather
0

Há outro caso em que git rebase --ontoé difícil entender: quando você refaz um commit resultante de um seletor de diferença simétrica (os três pontos ' ...')

O Git 2.24 (quarto trimestre de 2019) faz um trabalho melhor ao gerenciar esse caso:

Consulte commit 414d924 , commit 4effc5b , commit c0efb4c , commit 2b318aa (27 de agosto de 2019) e commit 793ac7e , commit 359eceb (25 de agosto de 2019) de Denton Liu ( Denton-L) .
Ajudado por: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) e Johannes Schindelin ( dscho) .
Consulte commit 6330209 , commit c9efc21 (27 de agosto de 2019) e commit 4336d36 (25 de agosto de 2019) por Ævar Arnfjörð Bjarmason (avar ).
Ajudado por: Eric Sunshine ( sunshineco) ,Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) e Johannes Schindelin ( dscho) .
(Mesclado por Junio ​​C Hamano - gitster- in commit 640f9cd , 30 set 2019)

rebase: avanço rápido --onto em mais casos

Antes, quando tínhamos o seguinte gráfico,

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

A execução ' git rebase --onto master... master side' resultaria em Dsempre uma reestruturação, não importa o quê.

Neste ponto, leia " Quais são as diferenças entre pontos duplos ' ..' e pontos triplos" ..."nos intervalos de confirmação do Git diff? "

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

Aqui: " master..." refere-se a master...HEAD, que é B: HEAD está do lado HEAD (atualmente com check-out): você está refazendo o B.
O que você está refazendo? Qualquer commit que não esteja no master e acessível a partir do sidebranch: existe apenas um commit que se encaixa nessa descrição: D... que já está no topo B!

Novamente, antes do Git 2.24, esse rebase --ontoresultado seria Dsempre rebatizado, não importa o quê.

No entanto, o comportamento desejado é que a rebase observe que isso é encaminhado rapidamente e faça isso.

Isso é semelhante ao rebase --onto B Ado OP, que não fez nada.

Adicione a detecção para can_fast_forwardque este caso possa ser detectado e um avanço rápido será realizado.
Primeiro, reescreva a função para usar gotos, o que simplifica a lógica.
Em seguida, desde o

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

condições foram removidas cmd_rebase, reintroduzimos um substituto em can_fast_forward.
Em particular, verificar as bases de mesclagem upstreame headcorrigir um caso com falha t3416.

O gráfico abreviado para t3416 é o seguinte:

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

e o comando com falha foi

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

Antes, o Git veria que havia uma base de mesclagem ( C, resultado de master...topic) e a mesclagem e a para a mesma eram, portanto, retornaria 1 incorretamente, indicando que poderíamos avançar rapidamente. Isso faria com que o gráfico reformulado fosse ' ABCFG' quando esperávamos ' ABCG'.

A rebase --onto C F topicsignifica qualquer commit depois F , acessível por topicHEAD: isto é Gapenas, não Fele próprio.
O avanço rápido nesse caso incluiria Fna ramificação rebaseada, o que está errado.

Com a lógica adicional, detectamos que a base de mesclagem e upstream é F. Como o onto não é F, significa que não estamos refazendo o conjunto completo de confirmações de master..topic.
Como estamos excluindo algumas confirmações, não é possível executar um avanço rápido e, portanto, retornamos 0 corretamente.

Adicione ' -f' aos casos de teste que falharam como resultado dessa alteração, porque eles não esperavam um avanço rápido, para que uma rebase fosse forçada.

VonC
fonte