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 b1
no 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 clone
e 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 origin
e 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 origin
repositó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 origin
começ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 origin
repositó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 pull
e 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 origin
repositó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 fetch
e 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 origin
repositó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.