Como faço para recuperar / sincronizar novamente após alguém enviar um rebase ou uma redefinição para um branch publicado?


88

Todos nós ouvimos que nunca se deve rebase um trabalho publicado, que é perigoso, etc. No entanto, não vi nenhuma receita postada sobre como lidar com a situação no caso de um rebase ser publicado.

Agora, observe que isso só é realmente viável se o repositório for clonado apenas por um grupo conhecido (de preferência pequeno) de pessoas, de modo que quem quer que faça o rebase ou redefinir possa notificar a todos que precisará prestar atenção na próxima vez que buscar(!).

Uma solução óbvia que eu vi funcionará se você não tiver nenhum commit local fooe for rebaseizado:

git fetch
git checkout foo
git reset --hard origin/foo

Isso simplesmente descartará o estado local de fooem favor de seu histórico de acordo com o repositório remoto.

Mas como alguém lida com a situação se tiver cometido mudanças locais substanciais naquele ramo?


1 para a receita simples do caso. É ideal para sincronização pessoal entre máquinas, especialmente se elas tiverem sistemas operacionais diferentes. É algo que deve ser mencionado no manual.
— Philip Oakley,

A receita ideal para sincronização pessoal é git pull --rebase && git push. Se você trabalhar masterapenas em , então isso quase infalivelmente fará a coisa certa para você, mesmo se você tiver rebaseado e empurrado na outra extremidade.
— Aristóteles Pagaltzis,

Como estou sincronizando e desenvolvendo entre um PC e máquinas Linux, descobri que usar um novo branch para cada rebase / atualização funciona bem. Eu também uso a variante git reset --hard @{upstream}agora que sei que o encantamento refspec mágico para "esqueça o que eu tenho / tive, use o que eu obtive do controle remoto" Veja meu comentário final em stackoverflow.com/a/15284176/717355
— Philip Oakley

Você será capaz, com Git2.0, de encontrar a origem antiga de seu branch (antes do branch upstream ser reescrito com a push -f): veja minha resposta abaixo
— VonC

Respostas:


75

Voltar a sincronizar após um rebase forçado não é tão complicado na maioria dos casos.

git checkout foo
git branch old-foo origin/foo # BEFORE fetching!!
git fetch
git rebase --onto origin/foo old-foo foo
git branch -D old-foo

Ie. primeiro você configura um marcador para onde o branch remoto estava originalmente, então você o usa para reproduzir seus commits locais daquele ponto em diante no branch remoto rebaseizado.

Rebasing é como violência: se não resolve o seu problema, você só precisa de mais. ☺

Você pode fazer isso sem o marcador, é claro, se procurar o origin/fooID de confirmação pré-rebase e usá-lo.

É assim também que você lida com a situação em que se esqueceu de fazer um marcador antes de buscar. Nada está perdido - você só precisa verificar o reflog do branch remoto:

git reflog show origin/foo | awk '
    PRINT_NEXT==1 { print $1; exit }
    /fetch: forced-update/ { PRINT_NEXT=1 }'

Isso imprimirá o ID de confirmação que origin/fooapontou antes da busca mais recente que mudou seu histórico.

Você pode então simplesmente

git rebase --onto origin/foo $commit foo

11
Nota rápida: eu acho que é bastante intuitivo, mas se você não conhece o awk bem ... aquele one-liner está apenas olhando para a saída da git reflog show origin/fooprimeira linha dizendo "fetch: force-update"; é isso que o git grava quando uma busca faz com que o branch remoto faça qualquer coisa, menos o avanço rápido. (Você também poderia fazer isso manualmente - a atualização forçada é provavelmente a coisa mais recente.)
— Cascabel

2
Não é nada como violência. A violência ocasionalmente é divertida
— Iolo

5
@iolo Verdade, rebasing é sempre divertido.
— Dan Bechard,

1
Como a violência, quase sempre evita rebase. Mas tenha uma ideia de como.
— Bob Stein

2
Bem, evite empurrar um rebase onde outros serão afetados.
— Aristóteles Pagaltzis

11

Eu diria que a recuperação da seção upstream rebase da página do manual git-rebase cobre praticamente tudo isso.

Realmente não é diferente de recuperar de seu próprio rebase - você move um branch e rebase todos os branches que o tinham em seu histórico para sua nova posição.


4
Ah, é verdade. Mas embora agora eu entenda o que ele diz, eu não teria entendido antes, antes de descobrir isso sozinho. E não há receita de livro de receitas (talvez com razão nessa documentação). Também defenderei que chamar o “caso difícil” de difícil é FUD. Sugiro que a história reescrita é trivialmente administrável na escala da maior parte do desenvolvimento interno. A maneira supersticiosa com que esse assunto é sempre tratado me irrita.
— Aristóteles Pagaltzis

4
@Aristotle: Você está certo ao dizer que é muito gerenciável, visto que todos os desenvolvedores sabem como usar o git, e que você pode comunicar-se efetivamente com todos os desenvolvedores. Em um mundo perfeito, esse seria o fim da história. Mas muitos projetos por aí são grandes o suficiente para que um rebase upstream seja realmente uma coisa assustadora. (E há lugares como meu local de trabalho, onde a maioria dos desenvolvedores nunca ouviu falar de um rebase.) Acho que a "superstição" é apenas uma forma de fornecer o conselho mais seguro e genérico possível. Ninguém quer ser aquele que causa um desastre no repo de outra pessoa.
— Cascabel

2
Sim, entendo o motivo. E eu concordo totalmente com isso. Mas há um mundo de diferença entre “não tente fazer isso se você não entende as consequências” e “você nunca deve fazer isso porque é mau”, e só isso eu discordo. É sempre melhor instruir do que instilar medo.
— Aristóteles Pagaltzis

@Aristotle: Concordo. Eu tento tender para o lado "certifique-se de que você sabe o que está fazendo", mas especialmente online, tento dar peso suficiente para que um visitante casual do Google perceba. Você está certo, muito provavelmente deveria ser atenuado.
— Cascabel

11

Começando com git 1.9 / 2.0 Q1 2014, você não terá de marcar a sua origem ramo anterior antes rebasing-lo no ramo montante regravados, conforme descrito em Aristóteles Pagaltzis 's resposta :
Veja cometer 07d406b e cometer d96855f :

Depois de trabalhar no topicramo criado com git checkout -b topic origin/master, o histórico do ramo de rastreamento remoto origin/masterpode ter sido rebobinado e reconstruído, levando a um histórico desta forma:

                   o---B1
                  /
  ---o---o---B2--o---o---o---B (origin/master)
          \
           B3
            \
             Derived (topic)

onde origin/masterusado para apontar para commits B3, B2, B1e agora aponta para B, e seu topicramo foi iniciado em cima dela de volta quando origin/masterestava em B3.

Este modo usa o reflog de origin/masterpara encontrar B3como o ponto de bifurcação, de modo que o topicpossa ser realocado em cima do atualizadoorigin/master por:

$ fork_point=$(git merge-base --fork-point origin/master topic)
$ git rebase --onto origin/master $fork_point topic

É por isso que o git merge-basecomando tem uma nova opção:

--fork-point::

Encontre o ponto em que uma ramificação (ou qualquer histórico que leve a <commit>) bifurcou-se de outra ramificação (ou qualquer referência) <ref>.
Isso não apenas procura pelo ancestral comum dos dois commits, mas também leva em consideração o reflog de <ref>para ver se a história que levou a <commit>bifurcou-se de uma encarnação anterior do ramo<ref> .


O git pull --rebasecomando " " calcula o ponto de bifurcação da ramificação sendo realocada usando as entradas de reflog da baseramificação " " (normalmente uma ramificação de rastreamento remoto) em que o trabalho da ramificação foi baseado, a fim de lidar com o caso em que a "base" ramo foi rebobinado e reconstruído.

Por exemplo, se o histórico fosse assim:

  • a dica atual do basebranch " " está em B, mas fetch anterior observou que sua dica costumava ser B3e then B2and then B1 antes de chegar ao commit atual, e
  • o branch sendo rebaseado no topo da "base" mais recente é baseado em commit B3,

ele tenta encontrar B3atravessando a saída do " git rev-list --reflog base" (ou seja B, B1, B2, B3) até encontrar uma confirmação de que é um antepassado da ponta atual " Derived (topic)".

Internamente, temos get_merge_bases_many()que computar isso de uma só vez.
Queríamos uma base de mesclagem entre Derivedum commit de mesclagem fictício que resultaria da mesclagem de todas as dicas históricas de " base (origin/master)".
Quando existe tal confirmação, devemos obter um único resultado, que corresponda exatamente a uma das entradas de reflog de " base".


Git 2.1 (Q3 2014) irá adicionar tornar este recurso mais robusto para isso: consulte commit 1e0dacd de John Keeping ( johnkeeping)

lidar corretamente com o cenário em que temos a seguinte topologia:

    C --- D --- E  <- dev
   /
  B  <- master@{1}
 /
o --- B' --- C* --- D*  <- master

Onde:

  • B'é uma versão corrigida do Bque não é idêntica ao patch B;
  • C*e D*são idênticos ao patch Ce Drespectivamente e conflitam textualmente se aplicados na ordem errada;
  • Edepende textualmente D.

O resultado correto da git rebase master devé que Bé identificado como o ponto de bifurcação do deve master, de modo que C, D, Esão os commits que precisam ser repetidos para master; mas Ce Dsão idênticos ao patch com C*e D*e podem ser eliminados, de modo que o resultado final seja:

o --- B' --- C* --- D* --- E  <- dev

Se o ponto de bifurcação não for identificado, pegar Bum branch contendo B'resulta em um conflito e se os commits idênticos ao patch não forem identificados corretamente, então pegar Cum branch contendo D(ou equivalentemente D*) resulta em um conflito.


O " --fork-point" modo de " git rebase" regrediu quando o comando foi reescrito em C na era 2.20, que foi corrigido com Git 2.27 (Q2 2020).

Veja o commit f08132f (09 de dezembro de 2019) de Junio ​​C Hamano ( gitster) .
(Incorporado por Junio ​​C Hamano - gitster- no commit fb4175b , 27 de março de 2020)

rebase: --fork-pointcorreção de regressão

Assinado por: Alex Torok
[jc: reformulou a correção e usou os testes de Alex]
Assinado por: Junio ​​C Hamano

" git rebase --fork-point master" costumava funcionar bem, porque era chamado internamente de " git merge-base --fork-point" que sabia como lidar com refname curto e defini-lo como refname completo antes de chamar a get_fork_point()função subjacente .

Isso não é mais verdade depois que o comando foi reescrito em C, pois sua chamada interna feita diretamente para get_fork_point()não desativa uma referência curta.

Mova a lógica "dwim the refname para o refname completo" que é usada em "git merge-base" para a get_fork_point()função subjacente , de modo que o outro chamador da função na implementação de "git rebase" se comporte da mesma maneira para corrigir esta regressão.


1
Observe que um git push --force pode agora (git 1.8.5) ser feito de forma mais prudente: stackoverflow.com/a/18505634/6309
— VonC
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.