Descentralizando o SVN com o SVK
Você já deve ter lido alguma coisa sobre o Git, Mercurial, Bazaar ou darcs. Leu e ficou vislumbrado com a possibilidade de desenvolver localmente, de criar revisões sem precisar fazer commit no repositório central e de poder trabalhar mesmo sem conexão com a rede local ou Internet. No entanto, por algum motivo você não pode contar com isso em breve: sua empresa não quer ter que treinar seus desenvolvedores em um novo sistema ou existem dificuldades de pessoal/infra-estrutura para a migração do velho e bom SVN a um desses novos (na verdade estão aí há um bom tempo!) sistemas de controle de código.

Então, você voltou à realidade e ao trabalho, pensando em que arquivos você deveria incluir no seu próximo SVN commit – uma bela correção de um bug daqueles de doer! – afinal, são tantos arquivos alterados no meio tempo que você não se lembra bem quais estão relacionados com a correção.
Você fechou os olhos e fez o commit. Alguns minutos depois, outro desenvolvedor notou que havia alguma coisa errada: você esqueceu um arquivo. Ok, novo commit, acontece. Outro erro encontrado, “ah, esqueci de adicionar um novo arquivo com uma nova interface”. Novo commit. Isso tudo porque você ficou acumulando diversas alterações não relacionadas e não anotou quais dos arquivos corrigia o tal bug. “Seria mais fácil com o git” – você resmunga de forma rabugenta e sem esperanças.
Mas tudo isso é possível também com o SVN: use o SVK. O SVK é um sistema de controle de código distribuído construído no topo do filesystem do Subversion e possibilita espalhamento de repositório, operacão off-line, merging com histórico e integração com outros sistemas de controle de código (CVS, Perforce e GNU arch). Foi criado em 2003 por Chia Liang Kao.
Eu descobri o SVK recentemente em uma situação semelhante ao do início do post. Desde então, percebi que o SVN não é nada ágil quando enfretamos uma situação como a da situação do bug que acabei de mencionar. Talk is cheap, vamos ao trabalho.
Observação: essa é a forma que tenho usado no dia a dia e existem outras aproximações. Foi a forma mais simples que achei para trabalhar.
Vou assumir que você já sabe como usar o SVN e o tem instalado. Vamos à tela preta, instale o SVK, no Debian/Ubuntu é tão fácil como:
# aptitude install svk
Usuários Mac com o Fink instalado também tem vida fácil:
# fink install svk
Usuários Windows tem isso. Só usei os pacotes Debian e Mac até hoje mas, fora a instalação, o uso não difere.
Criando o depot
O primeiro passo após a instalação é a criação de um depot path. É no depot que todos os arquivos do nosso mirror ficam armazenados e é basicamente um filesystem subversion:
eriksencosta$ svk depot -i Repository /Users/eriksencosta/.svk/local does not exist, create? (y/n)y
Para conferir se o depot foi criado, basta listar com a opção -l:
eriksencosta$ svk depot -l Depot Path ============================================================ // /Users/eriksencosta/.svk/local
Criando o mirror
Vamos ao nosso mirror. Vamos simular um repositório SVN tradicional mas, ao invés de checkouts via DAV, faremos sia svn:// por simplicidade. Nosso pequeno projeto de software é uma pequena aplicação CLI para cálculos de algumas sequências numéricas e de outras fórmulas matemáticas avançadas. Faça o download do arquivo fibonacci.py e crie um diretório chamado calcs/.
Primeiro, crie o seu repositório SVN e importe os arquivos:
$ cd ~/repos $ svnadmin create calcs $ svn mkdir file:///Users/eriksencosta/repos/calcs/trunk \ file:///Users/eriksencosta/repos/calcs/branch \ file:///Users/eriksencosta/repos/calcs/tag \ -m "Layout do repositório" $ cd ~/sandbox/calcs $ svn import . file:///Users/eriksencosta/repos/calcs/trunk -m "Import inicial"
Pronto. Agora podemos criar o nosso mirror. Um mirror vai ser o local de onde você fará os checkouts e commits. Iremos fazer um mirror do trunk do projeto:
$ svk mirror file:///Users/eriksencosta/repos/calcs/trunk //calcs/trunk
Veja a lista de mirrors com o parâmetro -l:
$ svk mirror --list Path Source ====================================================================== //calcs/trunk file:///Users/eriksencosta/repos/calcs/trunk
Pronto, mirror criado. Agora, sincronize-o para que seja feito o download de todo o conteúdo do repositório:
$ svk sync //calcs/trunk Syncing file:///Users/eriksencosta/repos/calcs/trunk Retrieving log information from 1 to 2 ############################################# 100.0%
Mirror criado e sincronizado. Vamos ao próximo passo?
Checkouts e commits
O primeiro passo para começar a usar o seu mirror é simplesmente fazer um checkout dele. Os comandos do SVK são praticamente os mesmos do SVN:
$ cd ~/sandbox $ rm -rf calcs $ svk co //calcs/trunk calcs Syncing //calcs/trunk(/calcs/trunk) in /Users/eriksencosta/sandbox/calcs to 4. A calcs/fibonacci.py
Pronto. A partir de então você usará comandos como o svk add (para adicionar um arquivo no controle de versão), svk del, svk cp, svk diff, svk info, entre outros, digite svk helo commands para mais detalhes. Em nosso arquivo fibonacci.py, dissemos que o software é open source e que mais informações podem ser lidas no arquivo LICENSE.txt. Vamos acrescentar esse arquivo então. Faça o download e coloque no diretório calcs/.
$ svk st ? LICENSE.txt
Agora, adicione o arquivo com svk add:
$ svk add LICENSE.txt A LICENSE.txt
Pronto. Agora é só fazer o commit no mirror! O comando para isso? Adivinhou: svk commit:
$ svk ci -m "Adicionado arquivo com cópia da licença GPL" Commit into mirrored path: merging back directly. Merging back to mirror source file:///Users/eriksencosta/repos/calcs/trunk. Merge back committed as revision 3. Syncing file:///Users/eriksencosta/repos/calcs/trunk Retrieving log information from 3 to 3 Committed revision 5 from revision 3.
Perceba que ao fazer o commit, o SVK sincronizou com o repositório SVN e mandou todas as atualizações do código. “Peraí! Isso é o mesmo que usar apenas o SVN”. Sim, é isso mesmo, usar o SVK dessa forma não faz sentido. Vamos ao próximo passo!
Branches e merges
Simplesmente fazer o commit no mirror SVK não faz sentido. Para obter as vantagens do desenvolvimento descentralizado, precisamos criar um branch. Iremos sempre fazer os commits no branch e só depois aplicar as atualizações no mirror. Para isso, usamos o svk copy:
$ svk cp -p -m "Branching de trabalho" //calcs/trunk //calcs/branch/working Committed revision 6.
O -p é o mesmo do mkdir, serve para criar todos os diretórios intermediários caso não existam. Agora, vamos usar o branch de agora em diante. Eu gosto de criar um novo diretório de trabalho e fazer um novo checkout, ajuda muito quando fazemos diversos branches durante o dia:
$ cd ~/sandbox $ svk co //calcs/branch/working calcs-working Syncing //calcs/branch/working(/calcs/branch/working) in \ /Users/eriksencosta/sandbox/calcs-working to 6. A calcs-working/LICENSE.txt A calcs-working/fibonacci.py
Pronto. Daqui em diante podemos prosseguir com o desenvolvimento. Vamos melhorar a descrição da mensagem de help do nosso script fibonacci.py:
13 help_message = ''' 14 Fibonacci calcs. 15 16 Cálculo da sequência de Fibonacci. Uso: 17 '''
Pronto. Verifique o que alterou com svk st:
$ svk st M fibonacci.py
Nada de novo. Até os códigos de status são os mesmos. Vamos ao commit!
$ svk ci -m "Atualizada mensagem de help do script de Fibonacci" Committed revision 7.
Agora sim! Perceba que não houve sincronização com o repositório SVN. Digamos que você tenha feitos mais alterações no código e que você acha que já está bom o suficiente para um commit no repositório SVN. Como fazer? Basta fazer um merge entre o branch e o mirror. Antes de tudo, é sempre bom verificar se teve alguma atualização no repositório, sincronizando o mirror com o repositório:
$ svk sync //calcs/trunk Syncing file:///Users/eriksencosta/repos/calcs/trunk
Aqui não tivemos nenhuma atualização. Vamos prosseguir fazendo o merge do nosso branch com o mirror. Para isso usaremos o comando svk smerge, primeiro, verificando com a opção -C quais serão as alterações realizadas no merge:
$ svk smerge -C //calcs/branch/working //calcs/trunk Auto-merging (0, 7) /calcs/branch/working to /calcs/trunk (base /calcs/trunk:5). Checking locally against mirror source file:///Users/eriksencosta/repos/calcs/trunk. U fibonacci.py New merge ticket: fc412678-8642-4466-9a15-73d9f0f1abf2:/calcs/branch/working:7
Apenas fibonacci.py foi alterado. Vamos ao smerge. Há algumas combinações de opções a se fazer. Eu uso duas, uma para quando trabalho com branches e outra quando faço o merge no mirror.
$ svk smerge -l --verbatim //calcs/branch/working //calcs/trunk
Esse merge pega todas as alterações do seu branch e aplica em seu mirror trunk como se fosse um commit só. A opção -l serve para usar o seu log local como mensagens de commit no repositório . É útil quando fazemos merge ao final do desenvolvimento de uma feature para o nosso branch de trabalho. Mais sobre isso daqui a pouco.
Esse próximo é mais adequado durante o merge para o mirror. Ele pega todas as revisões de seu mirror e cria no repositório SVN mantendo cada mensagem de commit no respectivo commit. O --verbatim serve para tirar o header e padding automático que o SVK coloca na mensagem de commit (com o seu nome de usuário e outros detalhes) e o I (i maiúsculo) é de commit incremental:
$ svk smerge -Il --verbatim //calcs/branch/working //calcs/trunk Auto-merging (0, 7) /calcs/branch/working to /calcs/trunk (base /calcs/trunk:5). ===> Auto-merging (0, 6) /calcs/branch/working to /calcs/trunk (base /calcs/trunk:5). Merging back to mirror source file:///Users/eriksencosta/repos/calcs/trunk. Empty merge. ===> Auto-merging (6, 7) /calcs/branch/working to /calcs/trunk (base /calcs/trunk:5). Merging back to mirror source file:///Users/eriksencosta/repos/calcs/trunk. U fibonacci.py New merge ticket: fc412678-8642-4466-9a15-73d9f0f1abf2:/calcs/branch/working:7 Merge back committed as revision 4. Syncing file:///Users/eriksencosta/repos/calcs/trunk Retrieving log information from 4 to 4 Committed revision 10 from revision 4.
Um pouco mais sobre branches
Os benefícios do SVK aparecem quando trabalhamos com branches. Aqui, branch não tem aquela conotação de ramificação paralela do software durante longos períodos de tempo e que levam a merges complicados. Nossos merges tem vida curta, geralmente duram apenas o suficiente para a correção de um bug ou implementação de nova feature.
“Qual a vantagem disso?” Não parece óbvia? Durante o ciclo de correção ou implementação, você poderá fazer commits e ter um histórico das alterações. Fica mais fácil trabalhar quando podemos fazer pequenos commits e voltar a última revisão antes de ter feito uma alteração equivocada. É bom experimentar! Digamos que no nosso conjunto de super-scripts, criássemos um para o cálculo da distância Euclidiana. Ficaria mais fácil criar um branch para a feature:
$ svk cp //calcs/trunk //calcs/branch/euclidiana -m \ "Branching para implementação da feature de cálculo da distância Euclidiana" Committed revision 11. $ svk ~/sandbox $ svk co //calcs/branch/euclidiana calcs-euclidiana Syncing //calcs/branch/euclidiana(/calcs/branch/euclidiana) in \ /Users/eriksencosta/sandbox/calcs-euclidiana to 11. A calcs-euclidiana/LICENSE.txt A calcs-euclidiana/fibonacci.py U calcs-euclidiana
Agora, iremos desenvolver nossa feature até ela ficar estável para um commit no mirror. Vamos apenas criar um arquivo python para manter a simplicidade do exemplo mas pense que você poderia ter alterado alguns arquivos, melhorado um pouco a sua API, criado novos unit tests, entre outras coisas relacionadas a implementação da feature:
$ cd calcs-euclidiana/ $ touch euclidiana.py $ svk st ? euclidiana.py $ svk add euclidiana.py A euclidiana.py $ svk ci -m "Nova feature de cálculo da distância Euclidiana" Committed revision 12.
É a hora do merge. Antes de tudo, sincronize o mirror com o repositório.
$ svk sync //calcs/trunk Syncing file:///Users/eriksencosta/repos/calcs/trunk
Eu gosto de fazer os merges sempre no branch de trabalho (working) antes de aplicar no mirror:
$ svk smerge -Il --verbatim //calcs/trunk //calcs/branch/working Auto-merging (5, 10) /calcs/trunk to /calcs/branch/working (base /calcs/branch/working:7). ===> Auto-merging (5, 10) /calcs/trunk to /calcs/branch/working (base /calcs/branch/working:7). Empty merge.
Depois, eu faço o merge do branch da feature para o branch working. Aqui eu uso o smerge com -l (L minúsculo) apenas e informo uma longa descrição da feature:
$ svk -l --verbatim //calcs/branch/euclidiana //calcs/branch/working Waiting for editor... Nova feature: cálculo da distância Euclidiana. Algumas mudanças na API foram feitas: ... A euclidiana.py New merge ticket: 0183f186-1a31-4ca8-97b4-aad0e5290d2c:/trunk:4 New merge ticket: fc412678-8642-4466-9a15-73d9f0f1abf2:/calcs/branch/euclidiana:12 Committed revision 13.
Agora, o merge é do branch working para o mirror. Simplesmente use o smerge com -Il --verbatim:
$ svk smerge -Il --verbatim //calcs/branch/working //calcs/trunk Auto-merging (7, 13) /calcs/branch/working to /calcs/trunk (base */calcs/branch/working:7). ===> Auto-merging (7, 13) /calcs/branch/working to /calcs/trunk (base */calcs/branch/working:7). Merging back to mirror source file:///Users/eriksencosta/repos/calcs/trunk. A euclidiana.py New merge ticket: fc412678-8642-4466-9a15-73d9f0f1abf2:/calcs/branch/euclidiana:12 New merge ticket: fc412678-8642-4466-9a15-73d9f0f1abf2:/calcs/branch/working:13 Merge back committed as revision 5. Syncing file:///Users/eriksencosta/repos/calcs/trunk Retrieving log information from 5 to 5 Committed revision 14 from revision 5.
Pronto! Se você tiver um Trac instalado e configurado, poderá navegar entre os changesets e ver os commits no repositório. O mais importante é que você obteve os benefícios de sistemas de controle de código distribuídos mesmo usando o SVN.
Experimente trabalhar fazendo estes pequenos branches. Apesar de parecer complicado e demorado, depois de algumas tentativas o processo torna-se bem rápido. Você pode instalar um Trac e iniciar um ambiente usando o nosso repositótio SVN para ver o log dos commits que fizemos no nosso exemplo.
O algoritmo de Fibonacci e da distância Euclidiana eu deixo como exercício de lógica e matemática =)
Links recomendados
- SVKBook – livro semelhante ao SVNBook, não está completo mas é uma ótima referência
- Using SVK Part 1/3 – série de artigos em inglês explicando com mais detalhes o uso diário do SVK
- AkitaOnRails Reloaded – post da época que o Rails man nipo-brasileiro ainda usava o SVK
- Screencast de uso do Git por Fábio Akita – tem uma parte muito boa onde ele descreve os benefícios do controle de versão descentralizado. Está um pouco antes da metade do screencast
A beleza das melhorias imperfeitas
Estava lendo o blog do James Shore (para quem não conhece, co-autor do recomendadíssimo The Art of Agile Development) quando li a seguinte pérola de sabedoria:
Greatness emerges from a relentless march of imperfect improvements. A tiny improvement today enables a better improvement tomorrow, which enables even more improvement the next day and the day after that. Before you know it, you have more fun working in your “legacy” codebase than on brand-new code. It’s possible! I’ve seen it.
Isso me fez parar para pensar em diversas atividades que andei fazendo no meu trabalho em um projeto de software livre (será assunto para outro post). Basicamente estou adotando algumas práticas de XP para a minha rotina de desenvolvimento. Tem sido uma experiência bastante agradável, principalmente porque os resultados apareceram rapidamente.
Existem dificuldades também, algumas por falta de experiência anterior com a metodologia mas a principal é comportamental: XP requer disciplina, principalmente ao seguir o máximo possível o timebox definido nas estimativas das histórias. Todas as vezes que lancei mão dessa disciplina porque queria terminar algo de uma vez só resultaram em fracasso retumbante. Atraso é igual a desperdício, pior ainda atraso que não resulte em software que funcione e/ou software que funcione mas não testado!
Voltando a Shore, nos últimos dois meses, estive bastante concentrado na busca por melhorias nos meus ciclos desenvolvimento. O projeto em que estou trabalhando tem uma base de código legada (legado é algo que não fiz, o que imediatamente se traduz a: poutz! – um sentimento profundo e dualista, onde não sabemos onde estamos nem para onde vamos). Qual foi a primeira coisa que fiz? Logo na primeira semana, me lancei a corrigir alguns bugs.
Em duas semanas, já tinha alguma documentação sobre coding standards, ao qual sigo toda vez que altero algum código. Em três semanas, já tinha os primeiros testes unitários (PHPUnit) e funcionais (Selenium) para checar se as correções foram bem sucedidas. Anteontem, atualizei a documentação para contemplar detalhes sugeridos pela FSF ao lançar um software sob GPL e, ontem, comecei a usar um script de build (Phing) para criar pacotes ZIPs e GZips para distribuição do software. Em quase todas as semanas consegui corrigir algum bug (mesmo que muitíssimo pequeno ou simples).
Claro que teve dedicação extra, apoio da gerencia (fundamental) e várias horas de estudo fora do trabalho para estudar soluções em PHP para a execução dessas atividades mas, o grande lab, foi o trabalho diário. Anteontem, fizemos nossa primeira release pública com a correção de um pouco mais de uma dúzia de bugs.
Há ainda um monte de coisa (integração contínua, versionamento do banco de dados, etc, etc) a fazer mas espero que tenha conseguido capturar a mensagem: é melhor ir melhorando aos poucos, de forma imperfeita, do que esperar meses até entregar a perfeição. Do que vale ficar perdendo tempo prevendo coisas (e criando um book de diagramas UMLs que ninguém irá atualizar ou consultar) que o seu cliente não precisa e que não terão utilidade? Adicione valor sempre, em pequenos ciclos de desenvolvimento, entregue valor!
Fontes e links da palestra na PHP Conference 2008
PDF da apresentação: Desenvolvimento Web com Drupal: o CMS e o framework
Links: aos poucos irei atualizando. Caso tenha algo específico que queira perguntar, mande e-mail ou comente que respondo o mais breve possível.