Git para Usuários do Subversion, Parte 2: Assumindo o Controle

Desvendando as complexidades de mesclagem de ramificação

Git oferece aos desenvolvedores de Linux® inúmeras vantagens em relação ao Subversion para controle de versão de software. Portanto, os desenvolvedores que trabalham de maneira colaborativa e detêm o seu conhecimento ficam familiarizados com os conceitos básicos que estão por trás dele. Nesta seção, Ted analisa minuciosamente a ramificação e a mesclagem no Git e no Subversion, apresenta o "git bisect" para divisão das alterações em duas partes e mostra como resolver conflitos de mesclagem.

Teodor Zlatanov, Programmer, Gold Software Systems

photo- teodor zlatanovTeodor Zlatanov surgiu com um M.S. em engenharia da computação na Universidade de Boston em 1999. É programador desde 1992, usando Perl, Java, C e C++. Seus interesses estão no trabalho de software livre em análise de texto, arquitetura de banco de dados, interfaces do usuário e administração de sistemas UNIX.



16/Dez/2009

Esta é a segunda parte de uma série de duas partes. Você deverá ler a Parte 1 caso ainda não a tenha lido, já que usarei a mesma configuração de Git e Subversion (SVN), além do que ela o deixará acostumado com o meu senso de humor.

Ramificação e mesclagem em SVN

É evidente que a maior fonte de problemas para gerentes de sistema de controle de versão (VCS) é a ramificação e a mesclagem. A grande maioria dos desenvolvedores prefere realizar todas as alterações no tronco. Assim que a ramificação e a mesclagem surgem, os desenvolvedores começam a reclamar, e o gerente de VCS começa a lidar com isso.

Para ser sincero com os desenvolvedores, a ramificação e a mesclagem são operações assustadoras. Os resultados não são sempre óbvios, e a mesclagem podem causar problemas por desfazer o trabalho de outras pessoas.

SVN gerencia bem o tronco, e muitos desenvolvedores não se incomodam com a ramificação. Os clientes SVN antes do 1.5 eram um pouco primitivos em relação ao controle de mesclagens, portanto, se você estiver acostumado com clientes SVN mais antigos, pode ser que não conheça a propriedade svn:mergeinfo de SVN.

Há também uma ferramenta chamada svnmerge.py (consulte Recursos para um link). A svnmerge.py pode controlar mesclagens sem o suporte de svn:mergeinfo e dessa forma funciona para clientes SVN mais antigos.

Devido à complexidade e às variações no suporte de mesclagem de SVN, não fornecerei exemplos específicos. Em vez disso, vamos falar sobre a mesclagem e a ramificação no Git. Você pode ler o manual de SVN mencionado na seção Recursos se estiver interessado.


Ramificação e mesclagem no Git

Se o Concurrent Versions System (CVS) for o idiota do vilarejo quando o assunto for ramificação e mesclagem, SVN será o vigário e Git, o prefeito. O Git foi desenvolvido praticamente para proporcionar ramificação e mesclagem fáceis. Esse recurso Git não só impressiona na demonstração, mas também mostra conveniência todos os dias.

Para dar a você um exemplo, o Git tem inúmeras estratégias de mesclagem, incluindo a chamada octopus (polvo), que permite mesclar várias ramificações de uma vez. Uma estratégia igual a de um polvo! Apenas pense na insanidade de tentar fazer esse tipo de mesclagem em CVS ou SVN. Git também oferece suporte a um tipo diferente de mesclagem denominado rebasing. Não examinarei rebasing aqui, mas é muito útil para simplificar o histórico do repositório, portanto, convém dar uma olhada nele.

Antes de continuar com o exemplo de mesclagem abaixo, você deverá se familiarizar com a configuração de ramificação descrita na Parte 1. Você tem HEAD (a ramificação atual, nesse caso master) e a ramificação empty-gdbinit. Primeiro, vamos mesclar empty-gdbinit com HEAD e efetuar uma alteração em HEAD e mesclá-la de outra forma em empty-gdbinit:

Listagem 1. Alterações de mesclagem de ramificação para HEAD com Git
# start clean
% git clone git@github.com:tzz/datatest.git
# ...clone output...
# what branches are available?
% git branch -a
#* master
#  origin/HEAD
#  origin/empty-gdbinit
#  origin/master
# do the merge
% git merge origin/empty-gdbinit
#Updating 6750342..5512d0a
#Fast forward
# gdbinit | 1005 ---------------------------------------------------------------
# 1 files changed, 0 insertions(+), 1005 deletions(-)
# now push the merge to the server
% git push
#Total 0 (delta 0), reused 0 (delta 0)
#To git@github.com:tzz/datatest.git
#   6750342..5512d0a  master -> master

Isso não é difícil. Você verá que master tem HEAD e, após a mesclagem com a ramificação empty-gdbinit, a ramificação master irá até o servidor remoto para sincronização com origin/master. Em outras palavras, você mesclou localmente de uma ramificação remota e enviou o resultado a outra ramificação remota.

O importante aqui é ver como Git não se importa em saber qual ramificação é a autorizada. Você pode mesclar de uma ramificação local para outra ou para uma ramificação remota. O servidor Git se envolve apenas em operações remotas. Por outro lado, SVN sempre requer o servidor SVN, porque, com SVN, o repositório no servidor é a única versão autorizada.

Claro, Git é um VCS distribuído, portanto, nada disso é surpresa. Ele foi projetado para funcionar sem autoridade central. Apesar disso, a liberdade pode ser um pouco complicada para os desenvolvedores acostumados com CVS e SVN.

Agora que estamos preparados com toda essa conversa grandiosa, vamos criar outra ramificação local:

Listagem 2. Criando e alternando para uma ramificação de release na máquina A
# create and switch to the stable branch
% git checkout -b release-stable
#Switched to a new branch "release-stable"
% git branch
#  master
#* release-stable
# push the new branch to the origin
% git push --all
#Total 0 (delta 0), reused 0 (delta 0)
#To git@github.com:tzz/datatest.git
# * [new branch]      release-stable -> release-stable

Agora, em uma máquina diferente, removeremos o arquivo gdbinit da ramificação master. Obviamente, não é necessário ser em uma máquina diferente, pode ser apenas em um diretório diferente, mas estou reutilizando a identidade "O Outro Ted" em Ubuntu da Parte 1 para a máquina B.

Listagem 3. Removendo gdbinit da ramificação master na máquina B
# start clean
% git clone git@github.com:tzz/datatest.git
# ...clone output...
% git rm gdbinit
# rm 'gdbinit'
# hey, what branch am I in?
% git branch
#* master
# all right, commit my changes
% git commit -m "removed gdbinit"
#Created commit 259e0fd: removed gdbinit
# 1 files changed, 0 insertions(+), 1 deletions(-)
# delete mode 100644 gdbinit
# and now push the change to the remote branch
% git push
#updating 'refs/heads/master'
#  from 5512d0a4327416c499dcb5f72c3f4f6a257d209f
#  to   259e0fda9a8e9f3b0a4b3019781b99a914891150
#Generating pack...
#Done counting 3 objects.
#Result has 2 objects.
#Deltifying 2 objects...
# 100% (2/2) done
#Writing 2 objects...
# 100% (2/2) done
#Total 2 (delta 1), reused 0 (delta 0)

Nada de errado aqui (exceto para "deltificar", que soa como algo que você faria na ginástica ou algo que um rio poderia fazer com uma grande quantidade de água). Mas o que acontece na máquina A na ramificação release-stable?

Listagem 4. Removendo a mesclagem de gdbinit da ramificação master para a ramificação release-stable na máquina
# remember, we're in the release-stable branch
% git branch
#  master
#* release-stable
# what's different vs. the master?
% git diff origin/master
#diff --git a/gdbinit b/gdbinit
#new file mode 100644
#index 0000000..8b13789
#--- /dev/null
#+++ b/gdbinit
#@@ -0,0 +1 @@
#+
# pull in the changes (removal of gdbinit)
% git pull origin master
#From git@github.com:tzz/datatest
# * branch            master     -> FETCH_HEAD
#Updating 5512d0a..259e0fd
#Fast forward
# gdbinit |    1 -
# 1 files changed, 0 insertions(+), 1 deletions(-)
# delete mode 100644 gdbinit
# push the changes to the remote server (updating the remote release-stable branch)
% git push
#Total 0 (delta 0), reused 0 (delta 0)
#To git@github.com:tzz/datatest.git
#   5512d0a..259e0fd  release-stable -> release-stable

A interface mentat, mencionada na Parte 1, esbarra de novo na diferença. Você deve saber que /dev/null é um arquivo especial que não contém nada e, assim, a ramificação master remota não tem nada, ao passo que a ramificação release-stable local tem um arquivo gdbinit. Isso não é sempre óbvio para a maioria dos usuários.

Após toda essa diversão, o pull mescla a ramificação local com origin/master, e o push atualiza origin/release-stable com as alterações. Como de costume, "delta" é a palavra favorita do desenvolvedor do Git—ele nunca perde a chance de usá-la.


Dividindo as alterações em duas partes

Não entrarei em detalhes sobre o comando git bisect aqui porque ele é muito complicado, mas eu queria mencioná-lo porque é uma ferramenta extraordinária. A divisão das alterações em duas partes é na verdade uma pesquisa binária no log de cometimento. "Binário" significa que a pesquisa divide o intervalo de pesquisa no meio e testa a metade toda vez para decidir se o segmento desejado está acima ou abaixo da metade.

A forma como ele funciona é simples. Você pode informar a Git que a versão A é boa, e a versão Z é ruim. O Git então perguntará (ou pedirá um script automatizado) se a versão que estiver no meio do caminho entre A e Z, digamos Q, estiver ruim. Se Q estiver ruim, o cometimento ruim estará entre A e Q; caso contrário, o cometimento ruim estará entre Q e Z. O processo será repetido até o cometimento ruim ser encontrado.

É especialmente ótimo o fato de a divisão em duas partes poder ser automatizada com um script de teste. Isso torna possível escrever um teste para a versão Z e usá-lo de maneira inversa para verificar onde um recurso foi interrompido, o que a maioria dos desenvolvedores chamaria de teste de regressão automatizado. Isso poupará o seu tempo.


Resolvendo conflitos

Os conflitos de mesclagem são inevitáveis em qualquer VCS, especialmente em um VCS distribuído como o Git. O que acontecerá se duas pessoas alterarem um arquivo de formas conflitantes na mesma ramificação? Os dois exemplos a seguir estão na ramificação master do repositório datatest que estamos usando há bastante tempo.

Primeiro, fazemos uma alteração para encode.pl na máquina B:

Listagem 5. "Não funciona" na máquina B
# we're at time T1
# change the contents
% echo "# this script doesn't work" > encode.pl
% git commit -a -m 'does not work'
#Created commit e61713b: does not work
# 1 files changed, 1 insertions(+), 1 deletions(-)
# we're at time T2 now, what's our status?
% git status
# On branch master
#nothing to commit (working directory clean)

Agora, fazemos uma alteração para encode.pl na máquina A sem reconhecimento das alterações na máquina B, e fazemos o seu push:

Listagem 6. "Funciona" na máquina A
# we're at time T2
# change the contents
% echo "this script does work" > encode.pl
% git commit -a -m 'does not work'
#Created commit e61713b: does not work
# 1 files changed, 1 insertions(+), 1 deletions(-)
# we're at time T3 now, what's our status?
% git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
#nothing to commit (working directory clean)
% git push
#Counting objects: 5, done.
#Delta compression using 2 threads.
#Compressing objects: 100% (2/2), done.
#Writing objects: 100% (3/3), 298 bytes, done.
#Total 3 (delta 0), reused 0 (delta 0)
#To git@github.com:tzz/datatest.git
#   259e0fd..f949703  master -> master

Agora, na máquina B, fazemos um git pull e percebemos que as coisas não são tão maravilhosas:

Listagem 7. Uh-oh na máquina B
% git pull
#remote: Counting objects: 5, done.
#Compressing objects: 100% (2/2), done.)   
#remote: Total 3 (delta 0), reused 0 (delta 0)
#Unpacking 3 objects...
# 100% (3/3) done
#* refs/remotes/origin/master: fast forward to branch 'master' 
#   of git@github.com:tzz/datatest
#  old..new: 259e0fd..f949703
#Auto-merged encode.pl
#CONFLICT (content): Merge conflict in encode.pl
#Automatic merge failed; fix conflicts and then commit the result.
# the next command is optional
% echo uh-oh
#uh-oh
# you can also use "git diff" to see the conflicts
% cat encode.pl
#<<<<<<< HEAD:encode.pl
## this script doesn't work
#=======
#this script works
#>>>>>>> f9497037ce14f87ff984c1391b6811507a4dd86c:encode.pl

Essa situação é muito comum em SVN também. As alterações realizadas por outra pessoa estão em desacordo com a sua versão de um arquivo. Apenas edite o arquivo e faça o seu cometimento:

Listagem 8. Corrigindo e fazendo cometimento na máquina B
# fix encode.pl before this to contain only "# this script doesn't work"...
% echo "# this script doesn't work" > encode.pl
# commit, conflict resolved 
% git commit -a -m ''
#Created commit 05ecdf1: Merge branch 'master' of git@github.com:tzz/datatest
% git push
#updating 'refs/heads/master'
#  from f9497037ce14f87ff984c1391b6811507a4dd86c
#  to   05ecdf164f17cd416f356385ce8f5c491b40bf01
#updating 'refs/remotes/origin/HEAD'
#  from 5512d0a4327416c499dcb5f72c3f4f6a257d209f
#  to   f9497037ce14f87ff984c1391b6811507a4dd86c
#updating 'refs/remotes/origin/master'
#  from 5512d0a4327416c499dcb5f72c3f4f6a257d209f
#  to   f9497037ce14f87ff984c1391b6811507a4dd86c
#Generating pack...
#Done counting 8 objects.
#Result has 4 objects.
#Deltifying 4 objects...
# 100% (4/4) done
#Writing 4 objects...
# 100% (4/4) done
#Total 4 (delta 0), reused 0 (delta 0)

Isso era fácil, não? Vamos ver o que acontecerá na máquina A na próxima vez em que ela for atualizada.

Listagem 9. Corrigindo e fazendo cometimento na máquina B
% git pull
#remote: Counting objects: 8, done.
#remote: Compressing objects: 100% (3/3), done.
#remote: Total 4 (delta 0), reused 0 (delta 0)
#Unpacking objects: 100% (4/4), done.
#From git@github.com:tzz/datatest
#   f949703..05ecdf1  master     -> origin/master
#Updating f949703..05ecdf1
#Fast forward
# encode.pl |    2 +-
# 1 files changed, 1 insertions(+), 1 deletions(-)
% cat encode.pl
## this script doesn't work

Fast forward significa que a ramificação local alcançou a ramificação remota automaticamente porque não tinha nada novo para a ramificação remota. Em outras palavras, um fast forward indica que nenhuma mesclagem foi necessária; e todos os arquivos locais não eram mais recentes que o push mais recente da ramificação remota.

Por fim, eu devo mencionar o git revert e o git reset, que são muito úteis para desfazer um cometimento ou outras alterações na árvore do Git. Não há espaço para explicá-los aqui, mas certifique-se de que saiba como usá-los.


Conclusão

Este artigo abriu o conceito de mesclagem, mostrando como é manter as ramificações locais e remotas em duas máquinas e resolvendo conflitos entre eles. Eu também chamei a atenção para as mensagens do Git ainda enigmáticas e complicadas porque, em comparação com o SVN, o Git é muito mais detalhado e muito menos inteligível. Quando você une esse fato à sintaxe complexa dos comandos do Git, ele pode tornar o Git bastante intimidador para a maioria dos iniciantes. Entretanto, quando alguns conceitos básicos são explicados, o Git se torna muito mais fácil—até mesmo mais agradável!

Recursos

Aprender

Obter produtos e tecnologias

  • A svnmerge.py é uma ferramenta para automatizar o controle de mesclagem. Ela permite aos mantenedores de ramificações mesclar as alterações de (e para) as suas ramificações, e registra automaticamente quais alterações já foram mescladas.
  • Downloads, documentação e ferramentas Git estão disponíveis no site do Git.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre, Linux
ArticleID=457167
ArticleTitle=Git para Usuários do Subversion, Parte 2: Assumindo o Controle
publish-date=12162009