GIT: Muito Promissor

2007 September 22, 05:44 h - tags: git

No artigo anterior falei um pouco sobre GIT e ele parece mesmo promissor. Meu objetivo é não fazer uma migração big-bang. Não me interessa neste momento ter todo o trabalho de retirar meu Subversion do ar, instalar um servidor GIT, etc. Meu objetivo é ter um repositório local que seja: rápido, estável, confiável, que faça o arroz-com-feijão com competência. Só o tempo dirá. Mas os primeiros testes foram muito satisfatórios!

Instalar o GIT varia. Em Windows: não faço idéia. Em Linux: Google – ao que parece existem tarballs, pacotes para Yum, pacotes para Apt-get. Deve ser bem simples, façam sua lição de casa. Para Mac: MacPorts. Foi um pouco estranho, porque o git-svn parece que usa Perl5, e meu SVK que já estava instalado também. Acabei desinstalando o SVK e suas dependências e instalando novamente – deve ser o sono, já é bem tarde. Resumindo, acredito que a receita (para começar em um MacPorts limpo), seja algo assim:

sudo port install git-core +doc +svn

1
2
3
4
5
6
7
8
Considerando que todos conseguiram instalar, agora deve haver, junto com o bom e velho script svn, os scripts *git* e *git-svn*. O que eu quero fazer: usar o git para gerenciar um repositório local - de tal forma que eu possa trabalhar (fazer commits, branches, merges) no meu micro sem depender de uma conexão ao servidor e, ao final do dia, poder "empurrar" todos os changesets (não apenas a última modificação, mas todo o histórico do dia) de volta ao servidor Subversion. Este "link":https://wiki.bnl.gov/dayabay/index.php?title=Synchronizing_Repositories#GIT_and_SVN foi muito útil.

Eu fiz um micro teste (micro mesmo, ultra-simples) usando o subversion de um projeto no SVN do Google Code.

<macro:code>mkdir [diretorio_local_do_projeto]
cd [diretorio_local_do_projeto]
git-svn init --username [seu_username] [url_do_seu_svn]/trunk

Isso inicia o repositório local do git e associa ao SVN remoto. A partir daqui existem várias opções, leiam neste artigo para entender as alternativas. Dentre as principais está fazer uma cópia completa do repositório – o que significa todas as revisões -, ou fazer um download apenas do HEAD do seu trunk. Se o projeto for pequeno, tanto faz. Se o histórico for importante, será necessário replicar tudo, ou replicar a partir de uma determinada revisão.

Um aviso: puxar muitas revisões (sei lá, 500, 1000, 10 mil?) pode demorar MUITAS horas. Mesmo meu projetinho, com menos de 100 revisões, sob uma conexão de 8Mbit levou alguns minutos. Felizmente é possível pegar a partir de uma revisão até o HEAD, assim:

git-svn fetch -rXXX
1
2
3
4
5
6
Onde "XXX" é o número da revisào a partir da qual você quer pegar. Se quiser apenas o HEAD, pegue apenas a última. Não sabe qual o número? Oras "svn log --limit XX" para puxar o log das últimas XX revisões do seu SVN.

Uma recomendação do artigo é criar um novo branch - questões cosméticas? Aliás, gerenciar branches - que é algo que fazemos com muita cerimônia no SVN - deve ser rotina no GIT: faça quantas quiser, quando quiser. Faça um por arquivo se for maluco. Diz a propaganda que o GIT aguenta. Na prática existem muitos recursos do SVN que usamos raramente porque sabemos que pode trazer dores-de-cabeça. E se pensarmos melhor, oras, se precisamos planejar tanto para usar um recurso é porque ele é muito mal feito. Todo bom recurso deveria ser à prova de noobies. Veremos nos próximos dias se o GIT sobrevive à propaganda (espero que sim). Enfim, criar branches:

<macro:code>git checkout --track -b [qualquer_nome] git-svn

“git-svn” é o nome do branch que o script “git-svn” criou automaticamente. Fora ele, por default, todo repositório tem pelo menos um branch, chamado “master”. Ao criar um branch, ele automaticamente se torna o novo default. Para ver todos os branches disponíveis use:

git branch -a
1
2
3
4
Já podemos brincar. Posso alterar meu código localmente neste novo working copy. Toda vez que precisar fazer um commit, basta fazer:

<macro:code>git commit -a -m "[seu comentário]"

Note que esses commits não estão indo ao servidor. Está tudo local. Para ver as revisões, o comando é parecido com o SVN:

git log
1
2
3
4
Finalmente, depois de alterar meu working copy como quiser, digamos que estou pronto para publicar as modificações de volta ao SVN:

<macro:code>git-svn dcommit

Isso deve fazer um replay do histórico deste o último fetch de volta ao servidor, teoricamente mantendo o histórico intacto, com o autor, os comentários dos commits e tudo mais.

Agora, no meu antigo working copy do SVN, fiz algumas pequenas modificações e depois svn commit. No novo working copy do GIT, para fazer o equivalente ao antigo “svn up”, faço o seguinte:

git-svn fetch
git-svn rebase
1
2
3
4
5
6
7
8
9
Isso trará todas as revisões. O interessante é que parece que ele faz um "rollback" local e dá um "replay" nas minhas próprias revisões sobre as que vieram do SVN. Ainda não entendi bem o algoritmo, mas isso parece mais eficiente do que um merge bidimensional apenas do último snapshot local (working copy) contra os deltas do HEAD do servidor. Um conceito que parece importante é que o GIT - ao contrário de todos os outros - não rastreia arquivos, ele *rastreia conteúdo*. Segundo Linus Torvalds, isso é muito mais importante. Por exemplo, você consegue rastrear de onde veio uma determina linha, ou trecho: de qual arquivo ele veio, depois quem fez o "copy and paste" e para que arquivo ele foi parar. Todo o histórico. Fazer um blame nunca foi tão fácil.

Enfim. Ao final fiz um teste mais interessante: no working copy do SVN, eu movi um arquivo de lugar, um básico "mv". Isso significa:

<macro:code>
svn delete [arquivo antigo]
svn add [arquivo novo]
svn commit -m ""

No working copy do GIT, antes de fazer o fetch, eu peguei o mesmo arquivo, que ainda está com o nome antigo, e alterei seu conteúdo. Se fosse no SVN, aconteceria o seguinte: o svn up traria a ordem de deleção. Como o arquivo foi modificado ele não seria deletado. O novo arquivo viria e você acabaria com o novo e com o antigo, agora fora do repositório (marcado como “?”).

E no GIT? Bem, depois to fetch/rebase ele foi inteligente o suficiente para saber que o arquivo antigo foi renomeado! Ele trouxe a já aplicou a mudança que havia feito localmente no GIT!! E não me deixou com sujeira para rastrear, copiar do antigo, colar no novo e apagar o arquivo velho, como o SVN faria. Isso foi interessante. Só esse recurso já me convenceu.

E isso porque ainda nem comecei a raspar na casca. Como agora tenho um histórico local, digamos que estou na praia, sem internet, trabalhando desconectado mas mesmo assim fazendo commits :-) (wishful thinking, eu sei) mas suponha que eu pense “Putz, não era isso que eu queria comitar” mas agora eu posso reverter commits até a revisão anterior que eu queira. Vejam neste tutorial sobre como trabalhar com o recurso de reversão.

O pessoal do Samba e do Gnome tem tutoriais parecidos. No primeiro caso, parece que o Samba agora fica gerenciado em GIT, então dá para ter uma idéia de como é trabalhar num ambiente puramente GIT. No caso do Gnome eles ensinam como colaborar com GIT mas interfaceando com a infraestrutura SVN (gerar patches e tudo mais). E aí mais um truque: em vez de usar “init” como fiz, eles usam “clone” e ao final há um passo extra: mudar de volta ao branch “master”, fazer um “merge” com seu branch atual e só depois “dcommit”. Parece que eles preferem fazer merge no master antes de enviar os commits ao SVN. No fundo acho que é a mesma coisa do que falei aqui.

E falando em merge, veja o que acontece se fizer o merge entre o branch “working”, que modifiquei nos passos acima, e o “master”:

> git checkout master
> git merge -squash working
Updating 780040f..00e988e
Fast forward
Squash commit -
not updating HEAD
init.rb | 4 -
lib/{fixes.rb => activerecord_fixes.rb} | 66 +
+++++++++++++++++++

1
 2 files changed, 47 insertions(+), 23 deletions(-)

Notem como ele realmente entendeu que aconteceu um rename de arquivos. Também mostra quantas linhas mudaram em cada um. Ainda estou surpreso que a modificação que fiz no “fixes.rb” – do working copy do GIT – foi parar corretamente e automaticamente dentro do “activerecord_fixes.rb”, que foi o arquivo renomeado vindo do SVN. E pelo que li, se deletar um arquivo usando “rm” ele automaticamente entende que é para tirar do repositório, diferente do svn onde precisamos explicitamente usar “svn delete”, “svn mv”, etc. Para adicionar novos arquivos, é igual: “git add [arquivo]”.

Aliás, o comando “git checkout” é semelhante ao “svn switch”. Não sei como ele faz, mas este comando foi instantâneo! Tecnicamente significa que agora meu working copy é o “master” e não tem as modificações que fiz no “working”. Pra isso ele teve que remontar minha estrutura local. No SVN não é nem de perto tão rápido: para começar que ele precisa se comunicar com o servidor, só no tempo que leva para conectar ao servidor para puxar o update, o GIT já acabou o trabalho. Aliás, switch é outra coisa que pode dar dor de cabeça. Nunca confiei muito nesse comando no SVN. Pra começar que você precisa ter feito commit de tudo antes do switch. No GIT, ele transfere as modificações que ainda não sofreram commit entre branches, durante o switch! Muito esperto. Ou seja, posso alterar arquivos, não fazer commit, ir tocando de branches e só comitar quando eu escolher o branch que eu queria!

No fim, este ultra-micro-teste foi muito interessante, bem sucedido e me deixou muito satisfeito. Não deu nenhum problema inesperado, as mensagens do GIT foram muito solícitas e fáceis de entender e ele fez muito melhor do que eu esperava. Significa que vou usá-lo por mais algum tempo.

Comments

comentários deste blog disponibilizados por Disqus