Como o git se funde após a escolha da cereja?

190

Vamos imaginar que temos um masterramo.

Então criamos um newbranch

git checkout -b newbranch

e faça dois novos commits para newbranch: commit1 e commit2

Então nós mudamos para dominar e fazer cherry-pick

git checkout master
git cherry-pick hash_of_commit1

Examinando gitk, vemos que o commit1 e sua versão escolhida com cereja têm hashes diferentes; portanto, tecnicamente, eles são dois commits diferentes.

Finalmente, nos fundimos newbranchem master:

git merge newbranch

e veja que esses dois commits com hashes diferentes foram mesclados sem problemas, embora impliquem que as mesmas alterações devam ser aplicadas duas vezes; portanto, um deles deve falhar.

O git realmente faz uma análise inteligente do conteúdo do commit durante a mesclagem e decide que as alterações não devem ser aplicadas duas vezes ou esses commit são marcados internamente como vinculados?

Paulo
fonte

Respostas:

131

Resposta curta

Não se preocupe, o Git cuidará disso.

Resposta longa

Diferentemente do SVN 1 , o Git não armazena confirmações no formato delta, mas é baseado em instantâneos 2,3 . Enquanto o SVN tentaria ingenuamente aplicar cada confirmação mesclada como um patch (e falhar, pelo motivo exato que você descreveu), o Git geralmente é capaz de lidar com esse cenário.

Ao mesclar, o Git tentará combinar os instantâneos de ambos os commit HEAD em um novo instantâneo. Se uma parte do código ou arquivo for idêntica nos dois instantâneos (ou seja, porque um commit já foi escolhido de maneira correta), o Git não tocará nele.

Fontes

1 Skip-Deltas no Subversion
2 Noções básicas sobre Git
3 O modelo de objeto Git

Helmbert
fonte
40
Na verdade, eu diria que você deve se preocupar com a fusão e "o git vai lidar com isso" não é uma boa regra de ouro.
Cregox
4
De fato, a mesclagem PODE resultar em conteúdo duplicado em alguns casos. O Git lida com isso às vezes, mas às vezes não.
Donquixote
Isso é terrivelmente errado. Got praticamente armazena os arquivos de arquivos em todas as formas possíveis. E se bem me lembro, o SVN costumava armazenar instantâneos.
he_the_great
2
@he_the_great, não. O formato de armazenamento skip-delta do SVN (! = Snapshots) está bem documentado no manual . E eu realmente não entendo o que você quer dizer com condenável . Não sou um falante nativo, mas tenho certeza de que não é uma palavra real.
helmbert
2
@he_the_great Mas, mesmo que os arquivos de pacote, qualquer hash para um arquivo resulta no arquivo completo. Sim, ele é compactado usando deltas, mas não é um delta para as alterações em uma confirmação, mas um delta entre hashes para um arquivo. No que diz respeito ao objeto de confirmação, ele está referenciando uma árvore que está referenciando o hash para um arquivo completo. O fato de os dados serem compactados não afeta o funcionamento do git. O Git armazena arquivos completos no que diz respeito a uma confirmação, o SVN armazena deltas para confirmações, pelo que entendi.
Peter Olson
43

Após essa mesclagem, você pode ter confirmados na história duas vezes.

Solução para evitar isso, cito um artigo que recomenda que ramificações com confirmações duplicadas (escolhidas com cerejeira) usem rebase antes da mesclagem:

git mesclar após git cherry-pick: evitando confirmações duplicadas

Imagine que temos o ramo mestre e um ramo b:

   o---X   <-- master
    \
     b1---b2---b3---b4   <-- b

Agora precisamos urgentemente dos commits b1 e b3 no master, mas não os demais commits em b. Então, o que fazemos é fazer o checkout do ramo principal e escolher cereja confirma b1 e b3:

$ git checkout master
$ git cherry-pick "b1's SHA"
$ git cherry-pick "b3's SHA"

O resultado seria:

   o---X---b1'---b3'   <-- master
    \
     b1---b2---b3---b4   <-- b

Digamos que fazemos outro commit no master e obtemos:

   o---X---b1'---b3'---Y   <-- master
    \
     b1---b2---b3---b4   <-- b

Se agora mesclarmos o ramo b no master:

$ git merge b

Obteríamos o seguinte:

   o---X---b1'---b3'---Y--- M  <-- master
     \                     /
      b1----b2----b3----b4   <-- b

Isso significa que as alterações introduzidas por b1 e b3 apareceriam duas vezes na história. Para evitar isso, podemos reorganizar em vez de mesclar:

$ git rebase master b

O que renderia:

   o---X---b1'---b3'---Y   <-- master
                        \
                         b2'---b4'   <-- b

Finalmente:

$ git checkout master
$ git merge b

nos dá:

   o---X---b1'---b3'---Y---b2'---b4'   <-- master, b

Correções de edição supostas pelo comentário de David Lemon

efêmero
fonte
1
ótima dica sobre rebase! 'pulará' automaticamente todos os commits escolhidos pela cereja.
iTake
2
Honestamente, parece bom demais para ser verdade, tenho que ver com meus olhos. Também REBASE modifica commits, sua última linha do tempo deve ser---Y---b2'---b4'
David Lemon
Funciona perfeitamente. Muito útil se você não quiser que os comensais sejam escolhidos duas vezes na história.
User2350644
1
Não se deve notar que, embora o rebase seja bonito, o perigo de usá-lo é que qualquer forquilha ou ramificação criada a partir do b antigo fique fora de sincronia, e os usuários podem precisar recorrer a coisas como git reset --hard e git push -f?
JHH
1
Isso é @JHH por isso que REBASE filial local aqui
ephemerr