Como e / ou por que a fusão no Git é melhor do que no SVN?

400

Ouvi em alguns lugares que uma das principais razões pelas quais os sistemas de controle de versão distribuídos brilham é uma fusão muito melhor do que em ferramentas tradicionais como o SVN. Isso é realmente devido a diferenças inerentes à forma como os dois sistemas funcionam, ou implementações específicas de DVCS como Git / Mercurial apenas possuem algoritmos de fusão mais inteligentes que o SVN?

Mr. Boy
fonte
Ainda não recebi uma resposta completa lendo as ótimas respostas aqui. Anunciado - stackoverflow.com/questions/6172037/...
ripper234
isso depende do seu modelo. em casos mais simples, o svn geralmente é melhor porque não chama acidentalmente mesclagens bidirecionais, como o git pode fazer se você pressionar / mesclar / puxar / pressionar em um único ramo de desenvolvimento. Veja: svnvsgit.com
Erik Aronesty

Respostas:

556

A alegação de por que a fusão é melhor em um DVCS do que no Subversion foi amplamente baseada em como as ramificações e mesclagens funcionaram no Subversion há algum tempo. O Subversion anterior à 1.5.0 não armazenava nenhuma informação sobre quando as ramificações foram mescladas; portanto, quando você queria mesclar, precisava especificar qual intervalo de revisões deveria ser mesclado.

Então, por que o Subversion é uma merda ?

Reflita sobre este exemplo:

      1   2   4     6     8
trunk o-->o-->o---->o---->o
       \
        \   3     5     7
b1       +->o---->o---->o

Quando queremos mesclar as alterações de b1 no tronco, emitiríamos o seguinte comando, estando em uma pasta com check-out de tronco:

svn merge -r 2:7 {link to branch b1}

… Que tentará mesclar as alterações b1no diretório de trabalho local. E então você confirma as alterações depois de resolver qualquer conflito e testar o resultado. Quando você confirma a árvore de revisão, fica assim:

      1   2   4     6     8   9
trunk o-->o-->o---->o---->o-->o      "the merge commit is at r9"
       \
        \   3     5     7
b1       +->o---->o---->o

No entanto, essa maneira de especificar faixas de revisões fica rapidamente fora de controle quando a árvore de versões cresce, pois o subversion não possui metadados sobre quando e quais revisões foram mescladas. Reflita sobre o que acontece depois:

           12        14
trunk  …-->o-------->o
                                     "Okay, so when did we merge last time?"
              13        15
b1     …----->o-------->o

Isso é amplamente um problema do design do repositório que o Subversion possui. Para criar uma ramificação, você precisa criar um novo diretório virtual no repositório que abrigará uma cópia do tronco, mas não armazena nenhuma informação sobre quando e o que as coisas foram mescladas de volta. Isso levará a desagradáveis ​​conflitos de mesclagem às vezes. O pior foi que o Subversion usou a fusão bidirecional por padrão, que tem algumas limitações incapacitantes na fusão automática quando duas cabeças de ramificação não são comparadas com seu ancestral comum.

Para mitigar esse Subversion agora armazena metadados para ramificação e mesclagem. Isso resolveria todos os problemas, certo?

E, a propósito, o Subversion ainda é péssimo ...

Em um sistema centralizado, como o subversion, os diretórios virtuais são ruins. Por quê? Porque todo mundo tem acesso para vê-los ... até os experimentais do lixo. Ramificar é bom se você quiser experimentar, mas não quer ver a experiência de todos e de suas tias . Este é um grave ruído cognitivo. Quanto mais ramos você adicionar, mais porcaria você verá.

Quanto mais ramificações públicas você tiver em um repositório, mais difícil será acompanhar todas as ramificações diferentes. Portanto, a pergunta que você terá é se o ramo ainda está em desenvolvimento ou se está realmente morto, o que é difícil dizer em qualquer sistema de controle de versão centralizado.

Na maioria das vezes, pelo que vi, uma organização usará como padrão uma grande ramificação de qualquer maneira. O que é uma pena, porque, por sua vez, será difícil acompanhar as versões de teste e lançamento e qualquer outra coisa boa que advenha da ramificação.

Então, por que os DVCS, como Git, Mercurial e Bazaar, são melhores que o Subversion na ramificação e fusão?

Há uma razão muito simples para isso: ramificação é um conceito de primeira classe . Não há diretórios virtuais por design e ramificações são objetos rígidos no DVCS, que precisam ser assim para funcionar simplesmente com a sincronização de repositórios (por exemplo, push and pull ).

A primeira coisa que você faz quando trabalha com um DVCS é clonar repositórios (git clone, hg clonee bzr branch). Conceitualmente, a clonagem é a mesma coisa que criar uma ramificação no controle de versão. Alguns chamam isso de bifurcação ou ramificação (embora a última também seja usada também para se referir a ramificações co-localizadas), mas é exatamente a mesma coisa. Todo usuário executa seu próprio repositório, o que significa que você tem uma ramificação por usuário .

A estrutura da versão não é uma árvore , mas um gráfico . Mais especificamente, um gráfico acíclico direcionado (DAG, ou seja, um gráfico que não possui ciclos). Você realmente não precisa se debruçar sobre as especificidades de um DAG, exceto que cada confirmação tenha uma ou mais referências pai (nas quais a confirmação foi baseada). Portanto, os gráficos a seguir mostrarão as setas entre as revisões ao contrário por causa disso.

Um exemplo muito simples de mesclagem seria este; imagine um repositório central chamado origine uma usuário, Alice, clonando o repositório em sua máquina.

         a…   b…   c…
origin   o<---o<---o
                   ^master
         |
         | clone
         v

         a…   b…   c…
alice    o<---o<---o
                   ^master
                   ^origin/master

O que acontece durante um clone é que todas as revisões são copiadas para Alice exatamente como eram (o que é validado pelos hash-ids identificáveis ​​de forma única) e marcam onde estão as ramificações da origem.

Alice então trabalha em seu repositório, comprometendo-se em seu próprio repositório e decide forçar suas alterações:

         a…   b…   c…
origin   o<---o<---o
                   ^ master

              "what'll happen after a push?"


         a…   b…   c…   d…   e…
alice    o<---o<---o<---o<---o
                             ^master
                   ^origin/master

A solução é bastante simples, a única coisa que o originrepositório precisa fazer é aceitar todas as novas revisões e mover seu ramo para a revisão mais recente (que o git chama de "avanço rápido"):

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

         a…   b…   c…   d…   e…
alice    o<---o<---o<---o<---o
                             ^master
                             ^origin/master

O caso de uso, que ilustrei acima, nem precisa mesclar nada . Portanto, o problema realmente não é com a mesclagem de algoritmos, pois o algoritmo de mesclagem de três vias é praticamente o mesmo entre todos os sistemas de controle de versão. A questão é mais sobre estrutura do que qualquer coisa .

Então, que tal você me mostrar um exemplo que tem uma verdadeira fusão?

É certo que o exemplo acima é um caso de uso muito simples, então vamos fazer um muito mais distorcido, embora mais comum. Lembra que origincomeçou com três revisões? Bem, o cara que os fez, vamos chamá-lo de Bob , está trabalhando sozinho e fez um commit em seu próprio repositório:

         a…   b…   c…   f…
bob      o<---o<---o<---o
                        ^ master
                   ^ origin/master

                   "can Bob push his changes?" 

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

Agora, Bob não pode enviar suas alterações diretamente para o originrepositório. Como o sistema detecta isso é verificando se as revisões de Bob descendem diretamente de origin's, o que neste caso não. Qualquer tentativa de empurrar resultará no sistema dizendo algo semelhante a " Uh ... Eu tenho medo não pode deixá-lo fazer isso Bob ."

Então, Bob precisa puxar e mesclar as alterações (com git's pull; ou hg's pulle merge; ou bzr's merge). Este é um processo de duas etapas. Primeiro, Bob precisa buscar as novas revisões, que as copiarão como são do originrepositório. Agora podemos ver que o gráfico diverge:

                        v master
         a…   b…   c…   f…
bob      o<---o<---o<---o
                   ^
                   |    d…   e…
                   +----o<---o
                             ^ origin/master

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

A segunda etapa do processo de extração é mesclar as dicas divergentes e confirmar o resultado:

                                 v master
         a…   b…   c…   f…       1…
bob      o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+
                             ^ origin/master

Espero que a mesclagem não entre em conflito (se você os antecipar, poderá executar as duas etapas manualmente no git com fetche merge). O que mais tarde precisa ser feito é enviar essas alterações novamente para origin, o que resultará em uma mesclagem de avanço rápido, pois a consolidação de mesclagem é um descendente direto das últimas no originrepositório:

                                 v origin/master
                                 v master
         a…   b…   c…   f…       1…
bob      o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+

                                 v master
         a…   b…   c…   f…       1…
origin   o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+

Há outra opção para mesclar git e hg, chamada rebase , que moverá as alterações de Bob para depois das alterações mais recentes. Como não quero que essa resposta seja mais detalhada, deixarei que você leia os documentos git , mercurial ou bazar sobre isso.

Como exercício para o leitor, tente desenhar como isso funcionará com outro usuário envolvido. É feito da mesma forma que o exemplo acima com Bob. A mesclagem entre repositórios é mais fácil do que você imagina, porque todas as revisões / confirmações são identificáveis ​​exclusivamente.

Há também a questão do envio de patches entre cada desenvolvedor, que foi um grande problema no Subversion, que é mitigado no git, hg e bzr por revisões únicas e identificáveis. Depois que alguém mescla suas alterações (ou seja, faz um commit de mesclagem) e o envia para que todos os outros membros da equipe consumam enviando para um repositório central ou enviando patches, eles não precisam se preocupar com a mesclagem, porque isso já aconteceu . Martin Fowler chama esse modo de trabalhar com a integração promíscua .

Como a estrutura é diferente do Subversion, ao empregar um DAG, ele permite que ramificações e mesclagens sejam feitas de maneira mais fácil, não apenas para o sistema, mas também para o usuário.

Spoike
fonte
6
Eu não concordo com seus ramos == argumento de ruído. Muitas ramificações não confundem as pessoas porque o desenvolvedor principal deve dizer às pessoas qual ramificação usar para grandes recursos ... então dois desenvolvedores podem trabalhar no ramo X para adicionar "dinossauros voadores", 3 podem trabalhar no Y para "permitir que você jogue" cars at people "
Mr. Boy
16
João: Sim, para um pequeno número de ramos, há pouco ruído e é gerenciável. Mas volte depois de ter testemunhado mais de 50 ramos e tags no subversion ou no caso claro em que a maioria deles você não pode dizer se eles estão ativos ou não. Problema de usabilidade das ferramentas à parte; por que ter todo esse lixo no seu repositório? Pelo menos na p4 (como o "espaço de trabalho" de um usuário é essencialmente uma ramificação por usuário), git ou hg você tem a opção de não deixar que todos saibam sobre as alterações que você faz até que você as empurre para cima, o que é uma segurança. vigie quando as mudanças são relevantes para os outros.
Spoike 18/03/10
24
Eu também não entendo o argumento "muitos ramos experimentais são ruído", @Spoike. Temos uma pasta "Usuários" onde cada usuário tem sua própria pasta. Lá, ele pode ramificar quantas vezes quiser. Ramos são baratos no Subversion e se você ignora as pastas dos outros usuários (por que você deveria se preocupar com eles de qualquer maneira), não vê barulho, mas, para mim, a fusão no SVN não é uma droga (e eu faço isso com frequência, e não, não é uma coisa pequena). . projeto) Então, talvez eu faça algo errado;) no entanto, a fusão de Git e Mercurial é superior e você apontou-out bem.
John Smithers
11
No svn é fácil matar ramificações inativas, você apenas as exclui. O fato de as pessoas não removerem galhos não utilizados e, portanto, criar desordem é apenas uma questão de limpeza. Você também pode acabar facilmente com muitas ramificações temporárias no Git. No meu local de trabalho, usamos um diretório de nível superior "temp-branches" além dos padrões - ramos pessoais e ramos experimentais entram lá em vez de desordenar o diretório branches onde as linhas de código "oficiais" são mantidas (nós não use ramos de recursos).
Ken10 Liu
10
Isso significa então que, a partir da versão 1.5 do subversion, pelo menos, pode mesclar-se, assim como o git?
Sam
29

Historicamente, o Subversion só conseguiu executar uma mesclagem direta de duas vias porque não armazenou nenhuma informação de mesclagem. Isso envolve pegar um conjunto de alterações e aplicá-las a uma árvore. Mesmo com informações de mesclagem, essa ainda é a estratégia de mesclagem mais usada.

O Git usa um algoritmo de mesclagem de três vias por padrão, que envolve encontrar um ancestral comum para as cabeças que estão sendo mescladas e usar o conhecimento que existe nos dois lados da mesclagem. Isso permite que o Git seja mais inteligente para evitar conflitos.

O Git também possui um código sofisticado de localização de renomeação, o que também ajuda. Ele não armazena conjuntos de alterações nem armazena informações de rastreamento - apenas armazena o estado dos arquivos em cada confirmação e usa heurísticas para localizar renomeações e movimentos de código conforme necessário (o armazenamento em disco é mais complicado que isso, mas a interface apresenta à camada lógica não expõe nenhum rastreamento).

Andrew Aylett
fonte
4
Você tem um exemplo de que o svn tem conflito de mesclagem, mas o git não?
Gqqnbig
17

Simplificando, a implementação da mesclagem é feita melhor no Git do que no SVN . Antes do 1.5, o SVN não registrava uma ação de mesclagem; portanto, era incapaz de realizar mesclagens futuras sem a ajuda do usuário, que precisava fornecer informações que o SVN não registrava. Com o 1.5, ele melhorou e, de fato, o modelo de armazenamento SVN é um pouco mais capaz que o DAG do Git. Mas o SVN armazenou as informações de mesclagem de uma forma bastante complicada que permite que as mesclagens levem muito mais tempo do que no Git - observei fatores de 300 no tempo de execução.

Além disso, o SVN alega rastrear renomeações para ajudar na mesclagem de arquivos movidos. Mas, na verdade, ainda os armazena como uma cópia e uma ação de exclusão separada, e o algoritmo de mesclagem ainda os tropeça em situações de modificação / renomeação, ou seja, onde um arquivo é modificado em uma ramificação e renomeado na outra, e essas ramificações são para ser mesclado. Tais situações ainda produzirão conflitos de mesclagem espúrios e, no caso de renomeação de diretório, ele ainda leva à perda silenciosa de modificações. (As pessoas do SVN tendem a apontar que as modificações ainda estão na história, mas isso não ajuda muito quando não estão em um resultado de mesclagem onde devem aparecer.

O Git, por outro lado, nem rastreia os renomeados, mas os descobre após o fato (no momento da mesclagem), e faz isso de maneira mágica.

A representação de mesclagem SVN também tem problemas; em 1.5 / 1.6, você poderia mesclar de tronco para ramificação quantas vezes quisesse, automaticamente, mas uma fusão na outra direção precisava ser anunciada ( --reintegrate) e deixar a ramificação em um estado inutilizável. Muito tempo depois, eles descobriram que esse não é realmente o caso, e que a) o --reintegrate pode ser descoberto automaticamente eb) repetidas mesclagens nas duas direções são possíveis.

Mas depois de tudo isso (que o IMHO mostra uma falta de entendimento do que eles estão fazendo), eu seria (OK, sou) muito cauteloso ao usar o SVN em qualquer cenário de ramificação não trivial e, idealmente, tentaria ver o que o Git pensa sobre o resultado da mesclagem.

Outros pontos apontados nas respostas, como a visibilidade global forçada das filiais no SVN, não são relevantes para mesclar recursos (mas para usabilidade). Além disso, as 'lojas Git mudam enquanto as lojas SVN (algo diferente)' estão fora de questão. Conceitualmente, o Git armazena cada commit como uma árvore separada (como um arquivo tar ) e depois usa algumas heurísticas para armazená-lo eficientemente. O cálculo das alterações entre duas confirmações é separado da implementação de armazenamento. O que é verdade é que o Git armazena o DAG histórico de uma forma muito mais direta que o SVN faz sua mergeinfo. Qualquer um que tentar entender o último saberá o que quero dizer.

Em poucas palavras: o Git usa um modelo de dados muito mais simples para armazenar revisões do que o SVN e, portanto, poderia colocar muita energia nos algoritmos de mesclagem reais, em vez de tentar lidar com a representação => praticamente melhor.

Andreas Krey
fonte
11

Uma coisa que não foi mencionada nas outras respostas, e que realmente é uma grande vantagem de um DVCS, é que você pode confirmar localmente antes de enviar suas alterações. No SVN, quando eu tinha alguma alteração, queria fazer check-in, e alguém já havia feito um commit no mesmo ramo nesse meio tempo, isso significava que eu tinha que fazer um svn updateantes de poder confirmar. Isso significa que minhas alterações e as alterações da outra pessoa agora estão misturadas e não há como interromper a mesclagem (como com git resetou hg update -C), porque não há confirmação para a qual retornar. Se a mesclagem não for trivial, isso significa que você não poderá continuar trabalhando no seu recurso antes de limpar o resultado da mesclagem.

Mas então, talvez isso seja apenas uma vantagem para as pessoas burras demais para usar ramificações separadas (se bem me lembro, tínhamos apenas uma ramificação usada para desenvolvimento na empresa em que usei o SVN).

daniel kullmann
fonte
10

EDIT: Isso está abordando principalmente essa parte da pergunta:
isso é realmente devido a diferenças inerentes no funcionamento dos dois sistemas ou implementações específicas do DVCS como Git / Mercurial têm algoritmos de fusão mais inteligentes que o SVN?
TL; DR - Essas ferramentas específicas têm algoritmos melhores. A distribuição tem alguns benefícios no fluxo de trabalho, mas é ortogonal às vantagens da fusão.
EDIT FIM

Eu li a resposta aceita. É simplesmente errado.

A fusão de SVN pode ser uma dor, e também pode ser complicada. Mas ignore como ele realmente funciona por um minuto. Não há informações que o Git mantenha ou possa derivar que o SVN também não mantenha ou possa derivar. Mais importante, não há motivo para manter cópias separadas (às vezes parciais) do sistema de controle de versão fornecerem informações mais reais. As duas estruturas são completamente equivalentes.

Suponha que você queira fazer "alguma coisa inteligente" em que o Git é "melhor". E sua coisa é verificada no SVN.

Converta seu SVN no formato Git equivalente, faça-o no Git e verifique o resultado, talvez usando várias confirmações, em algumas ramificações extras. Se você pode imaginar uma maneira automatizada de transformar um problema de SVN em um problema do Git, o Git não tem vantagem fundamental.

No final do dia, qualquer sistema de controle de versão me permitirá

1. Generate a set of objects at a given branch/revision.
2. Provide the difference between a parent child branch/revisions.

Além disso, para mesclar também é útil (ou crítico) saber

3. The set of changes have been merged into a given branch/revision.

Mercurial , Git e Subversion (agora nativamente, anteriormente usando svnmerge.py) podem fornecer todas as três informações. Para demonstrar algo fundamentalmente melhor com o DVC, aponte algumas quarta informações disponíveis no Git / Mercurial / DVC e não disponíveis no SVN / VC centralizado.

Isso não quer dizer que não sejam ferramentas melhores!

Pedro
fonte
11
Sim, eu respondi a pergunta nos detalhes, não no título. svn e git têm acesso à mesma informação (na verdade, normalmente o svn tem mais), então o svn pode fazer o que o git faz. Mas eles tomaram diferentes decisões de design e, na verdade, não. A prova no DVC / centralizado é que você pode executar o git como um VC centralizado (talvez com algumas regras impostas) e executar o svn distribuído (mas é uma merda). No entanto, isso é muito acadêmico para a maioria das pessoas - git e hg se ramificam e se fundem melhor que o svn. Isso é realmente o que importa ao escolher uma ferramenta :-).
Peter
5
Até a versão 1.5, o Subversion não armazenava todas as informações necessárias. Além do SVN pós-1.5, as informações armazenadas são diferentes: o Git armazena todos os pais de uma consolidação de mesclagem, enquanto o Subversion armazena em que revisões já foram mescladas na ramificação.
Jakub Narębski 23/03
4
Uma ferramenta que é difícil de reimplementar em um repositório svn é git merge-base. Com o git, você pode dizer "ramificações aeb divididas na revisão x". Mas o svn armazena "os arquivos foram copiados de foo para bar", então você precisa usar heurísticas para descobrir que a cópia para bar estava criando uma nova ramificação em vez de copiar arquivos dentro de um projeto. O truque é que uma revisão no svn é definida pelo número da revisão e pelo caminho base. Embora seja possível assumir "tronco" na maioria das vezes, ele morde se realmente houver galhos.
25412 Douglas
2
Re: "Não há informações que o git mantenha ou possa derivar que o svn também não mantenha ou possa derivar." - Eu descobri que o SVN não lembrava quando as coisas foram mescladas. Se você gosta de puxar o trabalho do tronco para o seu ramo e ir e voltar, a fusão pode se tornar difícil. No Git, cada nó em seu gráfico de revisão sabe de onde veio. Tem até dois pais e algumas mudanças locais. Eu confiaria que o Git seria capaz de mesclar mais do que o SVN. Se você mesclar no SVN e excluir a ramificação, o histórico da ramificação será perdido. Se você mesclar no GIT e excluir o ramo, o gráfico permanece e, com ele, o plug-in "culpado".
precisa
11
Não é o caso de que git e mercurial tenham todas as informações necessárias localmente, enquanto o svn precisa examinar os dados locais e centrais para derivar as informações?
22816 Warren Dew #
8

O SVN rastreia arquivos enquanto o Git rastreia as alterações de conteúdo . É inteligente o suficiente para rastrear um bloco de código que foi refatorado de uma classe / arquivo para outro. Eles usam duas abordagens diferentes para rastrear sua fonte.

Ainda uso muito o SVN, mas estou muito satisfeito com as poucas vezes em que usei o Git.

Uma boa leitura, se você tiver tempo: Por que escolhi o Git

used2could
fonte
É o que eu também leio, e é com isso que eu estava contando, mas na prática não está funcionando.
Rolf
Git rastreia o conteúdo de arquivos, ele só mostra o conteúdo como mudanças
Ferrybig
6

Basta ler um artigo no blog de Joel (infelizmente o último). Este é sobre o Mercurial, mas na verdade fala sobre as vantagens dos sistemas de VC distribuídos, como o Git.

Com o controle de versão distribuído, a parte distribuída não é realmente a parte mais interessante. A parte interessante é que esses sistemas pensam em termos de mudanças, não em termos de versões.

Leia o artigo aqui .

rubayeet
fonte
5
Esse foi um dos artigos que eu estava pensando antes de postar aqui. Mas "pensa em termos de mudanças" é um termo muito vago para o marketing (lembre-se que a empresa de Joel vende DVCS agora)
Sr. Boy
2
Também achei que era vago ... Sempre achei que o changesets era parte integrante das versões (ou revisões), o que me surpreende que alguns programadores não pensem em termos de mudanças.
Spoike 22/03/10
Para um sistema que realmente "pensa em termos de mudanças", dê uma olhada em Darcs
Max,
@ Max: claro, mas quando o assunto é empurrar, o Git entrega onde o Darcs é basicamente tão doloroso quanto o Subversion quando se trata de realmente se fundir.
Tripleee
As três desvantagens do Git são: a) não é tão bom para binários como gerenciamento de documentos, onde é muito improvável que as pessoas desejem ramificar e mesclar b) presume que você deseja clonar TUDO c) armazena o histórico de tudo no clone, mesmo para mudar os binários com frequência, causando inchaço no clone. Eu acho que um VCS centralizado é muito melhor para esses casos de uso. O Git é muito melhor para o desenvolvimento regular, principalmente para mesclagem e ramificação.
locka