Push Git rejeitado após rebase da ramificação de recurso

919

OK, achei que era um cenário simples, o que estou perdendo?

Eu tenho um masterramo e um featureramo. Faço alguns trabalhos master, outros featuree mais master. Termino com algo assim (ordem lexicográfica implica a ordem dos commits):

A--B--C------F--G  (master)
       \    
        D--E  (feature)

Não tenho nenhum problema para git push origin mastermanter o controle remoto masteratualizado, nem com git push origin feature(quando ativado feature) para manter um backup remoto para o meu featuretrabalho. Até agora, estamos bem.

Mas agora eu quero me refazer featuredos F--Gcommits no master, então eu git checkout featuree git rebase master. Ainda bom. Agora temos:

A--B--C------F--G  (master)
                 \
                  D'--E'  (feature)

Problema: no momento em que desejo fazer backup da nova featureramificação rebaseada git push origin feature, o push é rejeitado, pois a árvore foi alterada devido à rebasagem. Isso só pode ser resolvido com git push --force origin feature.

Eu odeio usar --forcesem ter certeza de que preciso. Então, eu preciso disso? O rebasing implica necessariamente que o próximo pushdeve ser --forcecumprido?

Esse ramo de recursos não é compartilhado com outros desenvolvedores; portanto, não tenho nenhum problema de fato com o impulso de força, não vou perder nenhum dado, a questão é mais conceitual.

Yuval Adam
fonte

Respostas:

682

O problema é que git pushassume que a ramificação remota pode ser encaminhada rapidamente para sua ramificação local, ou seja, que toda a diferença entre ramificações locais e remotas está no local, tendo alguns novos commit no final assim:

Z--X--R         <- origin/some-branch (can be fast-forwarded to Y commit)
       \        
        T--Y    <- some-branch

Quando você executa git rebaseconfirmações, D e E são aplicadas à nova base e novas confirmações são criadas. Isso significa que, após o rebase, você terá algo assim:

A--B--C------F--G--D'--E'   <- feature-branch
       \  
        D--E                <- origin/feature-branch

Nessa situação, a ramificação remota não pode ser encaminhada rapidamente para o local. Porém, teoricamente, a ramificação local pode ser mesclada em remota (obviamente você não precisa dela nesse caso), mas, como git pushexecuta apenas mesclagens de avanço rápido, lança e gera erros.

E o que a --forceopção faz é apenas ignorar o estado da ramificação remota e configurá-la para o commit que você está enviando. Então git push --force origin feature-branchsimplesmente substitui origin/feature-branchcom as autoridades locais feature-branch.

Na minha opinião, refazer a ramificação dos recursos mastere forçá-los a empurrá-los de volta para o repositório remoto é bom, desde que você seja o único que trabalha nesse ramo.

KL-7
fonte
68
Para ser sincero, puxar e mesclar a versão original do ramo de recursos ao rebased meio que elimina toda a idéia de rebasear.
KL-7
24
Talvez eu não tenha entendido você corretamente, mas se você puxar a ramificação do recurso, redimensioná-la para uma ramificação principal nova, não poderá empurrá-la para trás sem força, porque a versão remota da ramificação do recurso não pode ser encaminhada rapidamente para o novo versão (rebased) da ramificação do recurso. Isso é exatamente o que OP descreveu em sua pergunta. Se após o rebaseamento, mas antes do envio, git pull feature-branchessa solicitação gerará uma nova confirmação de mesclagem (mesclando as versões remota e local da ramificação do recurso). Portanto, ou você obtém uma mesclagem desnecessária após o rebaseamento ou pressiona --force.
KL-7
6
Ah, acho que entendi. Você está descrevendo a mesma abordagem que na resposta de Mark Longair. Mas gera uma confirmação de mesclagem. Pode ser útil em alguns casos, mas eu uso rebase principalmente em minhas próprias ramificações de recursos (portanto, push --forcenão é um problema) para manter o histórico de confirmações linear sem nenhuma confirmação de mesclagem.
KL-7
11
O problema com o "force-push" é que você realmente pode "perder coisas" (confirmações anteriores), algo que normalmente NUNCA deveria ser possível em qualquer sistema de controle de versão. Por esse motivo, pelo menos um ramo "master-ish" deve ter as configurações para não aceitar empurrões forçados , para limitar possíveis danos. (Cite um dos seguintes itens: funcionários mal-humorados / demitidos, idiotice própria, 'decisões' cansadas e sobrecarregadas de trabalho ...).
precisa
13
--force-with-leasecomo @hardev sugeriu é uma ótima opção
augustorsouza
466

Em vez de usar -f ou --force, os desenvolvedores devem usar

--force-with-lease

Por quê? Porque ele verifica se há mudanças na ramificação remota, o que é absolutamente uma boa ideia. Vamos imaginar que James e Lisa estejam trabalhando no mesmo ramo de recursos e Lisa tenha enviado um commit. James agora repassa sua filial local e é rejeitado ao tentar empurrar. É claro que James acha que isso se deve à reformulação e uso do --force e reescreveria todas as alterações de Lisa. Se James tivesse usado --force-with-lease, ele teria recebido um aviso de que há compromissos feitos por outra pessoa. Não vejo por que alguém usaria --force em vez de --force-with-lease ao pressionar após uma rebase.

Hardev
fonte
33
Ótima explicação. git push --force-with-leaseme salvou um monte.
Ckib16
5
Este é um comentário útil, mas não é realmente uma resposta para a pergunta.
Dallin
4
Essa é a resposta: refazer o controle para dominar / desenvolver cria um problema, é exatamente por isso que existe a força da concessão.
Tamir Daniely
3
Essa deve ser a resposta aceita. Resolve exatamente o problema descrito - força forçando sem forçar se alguém cometeu isso nesse meio tempo.
Luzian
3
Eu acho que tanto a resposta aceita quanto esta abordam a questão. A resposta aceita explica por que você precisa forçar. E este explica por que --force-with-leaseaborda a preocupação de usar--force
Jeff Appareti
48

Eu usaria "checkout -b" e é mais fácil de entender.

git checkout myFeature
git rebase master
git push origin --delete myFeature
git push origin myFeature

Quando você exclui, evita enviar uma ramificação existente que contém SHA ID diferente. Estou excluindo apenas a ramificação remota neste caso.

Eddy Hernandez
fonte
6
Isso funciona muito bem, especialmente se sua equipe tem um gancho git que rejeita todos os comandos git push --force.
Ryan Thames
1
obrigado por isso funcionou bem. Aqui estão mais detalhes sobre o que eu li para entender melhor. Isso é muito útil quando você não deseja ou não pode fazer força. Excluindo filiais remotas e Rebasing
RajKon
5
Isso tem o mesmo resultado que push --force, portanto, é apenas uma maneira de evitar um repositório Git --force. Como tal, não acho que essa seja uma boa idéia - nem o repo permite push --force, nem por um bom motivo que o desativa. A resposta de Nabi é mais apropriada se --forceestiver desabilitada no repositório remoto, pois não corre o risco de perder confirmações de outros desenvolvedores ou causar problemas.
Logan Captura
19

Uma solução para isso é fazer o que é msysGit merge rebasing script faz - depois que o rebase, merge no antigo chefe da featurecom -s ours. Você acaba com o gráfico de confirmação:

A--B--C------F--G (master)
       \         \
        \         D'--E' (feature)
         \           /
          \       --
           \    /
            D--E (old-feature)

... e seu impulso featureserá um avanço rápido.

Em outras palavras, você pode fazer:

git checkout feature
git branch old-feature
git rebase master
git merge -s ours old-feature
git push origin feature

(Não testado, mas acho que está certo ...)

Mark Longair
fonte
26
Acredito que o motivo mais comum de usar git rebase(em vez de mesclar masternovamente para o ramo de recursos) é criar histórico de confirmações lineares limpas. Com sua abordagem, o histórico fica ainda pior. E como o rebasing cria novos commits sem nenhuma referência às versões anteriores, nem tenho certeza de que o resultado dessa mesclagem será adequado.
KL-7
6
@ KL-7: O ponto principal merge -s oursé que ele adiciona artificialmente uma referência pai à versão anterior. Claro, a história não parece limpa, mas o interlocutor parece estar particularmente incomodado por ter que forçar o empurrão do featuregalho, e isso contorna isso. Se você deseja refazer a recuperação, é mais ou menos uma ou outra. :) De modo mais geral, eu acho que é interessante que o projeto msysgit faz isso ....
Mark Longair
@ KL-7: Aliás, adicionei a sua resposta com +1, que é claramente a certa - eu apenas pensei que isso poderia ser interessante também.
precisa saber é o seguinte
Definitivamente é interessante, pelo menos para mim. Obrigado. Já vi uma oursestratégia antes, mas achei que ela se aplica apenas a situações de conflito, resolvendo-as automaticamente usando alterações em nossa filial. Acabou que funciona de maneira diferente. E, dessa maneira, é muito útil se você precisar de uma versão reformulada (por exemplo, para que o mantenedor de repositórios a aplique de maneira limpa master), mas deseje evitar o esforço forçado (se muitas outras pessoas, por algum motivo, estiverem usando o seu ramo de recursos).
KL-7
15

Pode ou não ser o caso de haver apenas um desenvolvedor neste ramo, que agora (após o rebase) não está alinhado com a origem / recurso.

Como tal, sugiro usar a seguinte sequência:

git rebase master
git checkout -b feature_branch_2
git push origin feature_branch_2

Sim, novo ramo, isso deve resolver isso sem uma força -, o que eu acho que geralmente é uma grande desvantagem do git.

JAR.JAR.beans
fonte
3
Desculpe dizer, mas: "Continuar gerando desvios" para evitar a substituição forçada dos existentes não ajuda os desenvolvedores solitários de recursos (que podem substituir) nem várias pessoas que trabalham em um desvio de recurso (é necessário comunicar esse "incremento" e informar para passar, pessoal). - É mais como controle de versão manual ( “thesis_00.doc, thesis_01.doc, ...”), dentro de um sistema de controle de versão ...
Frank Nocke
2
Além disso, isso não ajuda quando você tem um PR do github aberto em um nome de filial, você precisa criar um novo PR para o novo nome da filial que você enviou.
gprasant
1
@ Frankkee Meia verdade da minha experiência. para um desenvolvedor solitário, sim, basta pressionar com força é bastante fácil, mas é o hábito que pode te morder mais tarde. + um novo desenvolvedor acabou de entrar? ou talvez algum sistema de CI que não esteja usando --hard reset? para uma equipe colaborando, acho que a comunicação do novo nome da filial é fácil, isso também pode ser feito com script + para uma equipe, sugiro que faça uma nova reformulação localmente ou quando a filial estiver pronta para mesclar, não durante o trabalho do dia a dia , a consolidação extra é menos problemática do que lidar com conflitos de rebase / mesclagem como resultado.
JAR.JAR.beans
@gprasant para PR, mais uma vez, acho que isso seria errado ao refazer, na verdade eu gostaria de ver o único commit com as correções de PR. Uma rebase (squash) deve ocorrer apenas mais tarde, como parte da mesclagem a ser dominada e quando o PR estiver pronto e pronto (para que nenhum novo PR precise ser aberto).
JAR.JAR.beans
13

Minha maneira de evitar o empurrão forçado é criar um novo ramo e continuar o trabalho nesse novo ramo e, após alguma estabilidade, remover o ramo antigo que foi rebatizado:

  • Rebaseando a ramificação com check-out localmente
  • Ramificação da ramificação rebased para uma nova ramificação
  • Enviando esse ramo como um novo ramo para remoto. e excluir a ramificação antiga no controle remoto
Nabi
fonte
1
Por que não há amor por essa opção? É definitivamente o mais limpo, mais simples, mais seguro.
cdmo
Como eu tenho cerca de 200 sistemas rastreando o nome da ramificação, e deve ser um nome específico para a tarefa, e se eu começar a fazer ramificações renomeadas a cada impulso, perderei a cabeça.
Tamir Daniely
@TamirDaniely Eu não tentei, mas excluir a ramificação antiga (do controle remoto) antes de empurrar e empurrar a nova ramificação com o mesmo nome antigo resolve seu problema?
Nabi
2
@ Nabi É exatamente isso que --force-with-lease faz, exceto que também verifica se não há novos commit que não são seus.
Tamir Daniely
12

Outros responderam sua pergunta. Se você refazer uma ramificação, precisará forçar a empurrá-la.

Rebase e um repositório compartilhado geralmente não se dão bem. Isso está reescrevendo o histórico. Se outras pessoas estiverem usando esse ramo ou se ramificarem nesse ramo, a recuperação será bastante desagradável.

Em geral, o rebase funciona bem para o gerenciamento de filiais locais. O gerenciamento de filial remota funciona melhor com mesclagens explícitas (--no-ff).

Também evitamos mesclar o mestre em um ramo de recurso. Em vez disso, redefinimos para master, mas com um novo nome de filial (por exemplo, adicionando um sufixo de versão). Isso evita o problema de rebasear no repositório compartilhado.

Bill Door
fonte
5
Você poderia adicionar um exemplo, por favor?
Thermech
8

O que há de errado com um git merge masterno featureramo? Isso preservará o trabalho que você teve, mantendo-o separado da ramificação da linha principal.

A--B--C------F--G
       \         \
        D--E------H

Edit: Ah, desculpe, não leu sua declaração de problema. Você precisará de força ao executar a rebase. Todos os comandos que modificam o histórico precisarão do --forceargumento. Isso é à prova de falhas para impedir que você perca o trabalho (o antigo De Eseria perdido).

Portanto, você executou um git rebaseque fez a árvore parecer (embora parcialmente oculta De Enão esteja mais em um ramo nomeado):

A--B--C------F--G
       \         \
        D--E      D'--E'

Então, ao tentar empurrar seu novo featureramo (com D'e E'dentro dele), você perderia De E.

Bouke
fonte
3
Não há nada errado com isso, e eu sei que vai funcionar. Não é apenas o que eu preciso. Como eu disse, a pergunta é mais conceitual do que prática.
usar o seguinte código
4

Para mim, seguir etapas fáceis funciona:

1. git checkout myFeature
2. git rebase master
3. git push --force-with-lease
4. git branch -f master HEAD
5. git checkout master
6. git pull

Depois de fazer tudo acima, podemos excluir o ramo myFeature também seguindo o comando:

git push origin --delete myFeature
Neeraj Khede
fonte
3

O seguinte funciona para mim:

git push -f origin branch_name

e não remove nenhum código meu.

Mas, se você quiser evitar isso, faça o seguinte:

git checkout master
git pull --rebase
git checkout -b new_branch_name

então você pode escolher todos os seus commit no novo ramo. git cherry-pick COMMIT ID e depois empurre seu novo ramo.

Sohair Ahmad
fonte
5
-fé um apelido para --force, que é o que a pergunta está tentando evitar, se possível.
epochengine 28/08/2015
1

Como o OP entende o problema, apenas procura uma solução melhor ...

Que tal isso como uma prática?

  • Tenha um ramo de desenvolvimento de recursos real (onde você nunca se recupera e pressiona com força, para que seus colegas desenvolvedores de recursos não o odeiem). Aqui, pegue regularmente essas alterações do main com uma mesclagem. História mais confusa , sim, mas a vida é fácil e ninguém fica interrompido em seu trabalho.

  • Tenha um segundo ramo de desenvolvimento de recursos, em que um membro da equipe de recursos regularmente empurra todos os recursos comprometidos para, de fato, reestruturados e até forçados. Tão quase limpo com base em um commit mestre bastante recente. Quando o recurso estiver concluído, empurre esse ramo em cima do mestre.

Talvez já exista um nome de padrão para esse método.

Frank Nocke
fonte