Archive for the 'controle de versão' Category

4 respostas sobre controle de versão distribuído

Entre idas e vindas de servidores, domínios e engines, já faz quase três anos que este blog existe. Em todo este tempo ainda não fiz nenhum texto com um título do tipo “10 razões para usar Emacs ao invés de Vi” que parece ser tão comum nesse mundo.

Isso precisava mudar e meu texto anterior sobre controle de versão parece ter servido como a desculpa perfeita. Surgiram algumas perguntas e observações nos comentários que me parecem ser bem freqüentes e resolvi compilar as perguntas e as respostas em um texto no formato 10-mais. As perguntas estão parafraseadas aqui e não foram necessariamente redigidas assim originalmente.

Vamos às questões:

Como as empresas podem usar sistemas distribuídos e ainda ter controle sobre seu código fonte?

Entendo a suposta necessidade das organizações de manter um controle rígido e centralizado do código-fonte. É bem verdade que muitas vezes elas fazem isso não tanto por necessidade, mas simplesmente por teimosia ou pela boa e velha força do hábito (também conhecida como inércia corporativa), mas admitamos que seja o caso de realmente haver a necessidade de um local central para se chamar de oficial.

Elas poderiam não usar nenhum sistema de controle de versão e ainda ter controle sobre o código. Basta dizer aos programadores que o código só deverá ser considerado pronto quando for enviado para determinado local. Este local pode ser um repositório de controle de versão centralizado, mas também pode ser um site FTP, um compartilhamento de rede qualquer ou um pendrive na mesa do chefe. Não importa.

A centralização do local de publicação oficial não exige a centralização do sistema de versionamento. Se as empresas simplesmente exigissem que as equipes disponibilizassem o código em algum lugar acessível dentro da rede interna, os programadores poderiam escolher qualquer método que quisessem para controlar suas modificações. Eles poderiam transferir arquivos de máquina em máquina ou guardar diffs manualmente, mas eu poderia apostar que a maioria deles optaria por um sistema de controle de versão qualquer. Muitas empresas não deixam a equipe escolher sozinha por preferir investir menos no processo de seleção dos funcionários e simplesmente assumir posteriormente que todos são tapados incapazes de limpar o próprio nariz. Quando todo mundo é idiota, é melhor minimizar os graus de liberdade para evitar que eles acabem colocando o dedo na tomada. É melhor exigir que todo mundo use exatamente os mesmos procedimentos e ferramentas. Mas esta é uma questão para um texto futuro.

Não há nada impedindo que os sistemas distribuídos de hoje sejam usados de forma centralizada. A equipe pode muito bem escolher um local para chamar de central e oficial. Eles podem fazer isso, mas não são obrigados. A questão é que há limitações que os sistemas centralizados têm que os distribuídos não têm. Não manter histórico das alterações das cópias de trabalho locais e exigir acesso de rede para commitar são só duas delas…

Como o controle de patches facilita os merges? Se há conflitos, eles precisam ser tratados independentemente do modelo de controle de versão adotado…

Se duas pessoas estão trabalhando no mesmo trecho de código, os conflitos certamente serão os mesmos não importa qual o sistema de controle de versão que usem. Neste caso, elas provavelmente precisam reestruturar o código para evitar pisar nos pés uma da outra. O controle de patches faz uma diferença maior quando os merges precisam ser feitos periodicamente.

O controle de versão por patches (e aí não estou necessariamente falando de controle distribuído, como mostra o texto anterior) evita que você precise lembrar quais as modificações que você já integrou. Um bom exemplo é quando você está fazendo merges sucessivos, como no caso do branch de desenvolvimento que precisa incorporar as modificações feitas no branch de conserto de bugs. Com controle de patches, você pode derivar uma base de código qualquer, fazer modificações locais e incorporar de tempos em tempos as modificações feitas no código original com muito menos dor de cabeça. Você não precisa se lembrar de que ponto do código original começou, nem até onde foi seu último merge. Desde que possua todas as modificações que o repositório original tem, você está atualizado.

Na média os usuários não querem colaborar. Eles contornam o sistema de controle de versão, compartilham código por outros meios e fazem todo tipo de porcaria.

Mais um argumento a favor dos sistemas distribuídos. Dessa vez não só dos não-seqüenciais, mas dos verdadeiramente distribuídos mesmo. A razão das pessoas estarem compartilhando código fora do controle de versão muitas vezes é o fato do código simplesmente não estar pronto para ser publicado. Ele não pode ir para o repositório central ainda, mas não é por isso que deve deixar de ser compartilhado.

Digamos que estou trabalhando em algum tipo de reestruturação extensiva. Para fazer essa reestruturação, eu preciso deixar alguns testes falhando por algum tempo. Eu sei que, quando terminar, os testes vão voltar a passar, mas precisam ficar quebrados enquanto eu termino. Qualquer reestruturação deste tipo pode ser sub-divida em pequenos objetivos. Portanto, faz sentido registrar alguns marcos parciais. Porém, se o sistema é centralizado, eu não posso fazer isso para não atrapalhar meus colegas. Minha única alternativa passa a ser bifurcar a base de código por algum tempo até que eu consiga terminar a reestruturação. Posso fazer isso por meio de branches ou nem usar o controle de versão e compartilhar código pela rede interna de algum outro modo.

Enquanto estou fazendo essa reestruturação, pode ser que você precise de algumas modificações que eu fiz. É em horas como essas que o pessoal começa a compartilhar código pela rede, fazer merge manual e usar de uma imaginação incrível para contornar as limitações do sistema. Com controle distribuído, cada desenvolvedor tem um ou mais repositórios próprios — essencialmente branches pessoais — que, em termos de recursos de versionamento, não deixam nada a desejar em relação ao repositório central. Se você precisa das minhas alterações, você simplesmente as importa diretamente do meu repositório. Não precisamos usar o repositório central como local de troca se não quisermos. Desse modo só nós dois precisamos lidar com aqueles testes falhando, e não a equipe toda.

Programadores ruins gostam de empurrar mouse e precisam de interfaces gráficas intuitivas. Nem todas equipes são bem capacitadas.

Sobre interface gráficas, este não é um problema inerente à distribuição e pode ser resolvido. Na verdade, eu acredito que já venha sendo resolvido. Eu particularmente não tenho problemas para trabalhar na linha de comando, portanto nem chego a procurar esse tipo de front-end. Então não posso dizer com toda a certeza que este problema não existe, mas provavelmente já deve estar bem perto de ser eliminado.

Interfaces intuitivas certamente beneficiam a todos — aos rock-stars também — e devem ser uma busca constante. Mas se uma equipe é pouco capacitada, pode acreditar que a falta de interfaces gráficas não é o maior dos problemas.

Por um controle de versão menos insano

Recentemente encontrei um texto do Jeff Atwood sobre a instalação do Subversion. Ele tem alguns argumentos bastante interessantes sobre controle de versão (sempre use controle de versão, faça modificações pequenas e blá, blá, blá…) que me parecem em geral bastante acertados, mas uma coisa me chamou a atenção: por que um programador (que pelo menos parece) tão bem informado quanto ele ainda escolhe um sistema de controle de versão tão completamente insano quando já existem tantas alternativas mais naturais para qualquer ser humano?

A maioria dos sistemas da velha guarda incrementa algum tipo de contador para identificar cada versão do objeto controlado: o Subversion, por exemplo, gera números de revisão quando há modificações em alguma parte da árvore de diretórios e o CVS associa um identificador seqüencial a cada um dos arquivos controlados. O maior problema do versionamento seqüencial é que simplesmente não controla o que queremos que controle. Saber quantas vezes um item qualquer foi modificado é útil, mas não tão útil quanto saber quais foram as modificações e, ainda melhor, quais as dependências entre elas. Sim, é bastante interessante saber qual o estado final de um trecho de código após um certo número de transformações, mas me interessa muito mais saber o que cada uma dessas modificações faz e tratar as modificações como objeto de trabalho, não os resultados delas.

Mais do que quantas modificações aconteceram desde algum ponto qualquer do tempo, eu preciso saber se a segunda modificação pode ser aplicada sem a primeira, se a terceira é uma inversão da primeira ou se a quinta desfaz algumas coisas que a quarta fez. As respostas para todas estas perguntas são necessárias para a operação mais fundamental do controle de versão: o merge.

Quando o Subversion surgiu, lá pelos idos de 2004, um de seus maiores argumentos era o branch barato. Este com certeza é um bom recurso para se ter, afinal de contas derivações precisam ser criadas o tempo todo. Mas o que queremos mesmo é que o merge seja fácil. O branch é só o começo da história. O merge é o ponto alto do processo, é quando as contribuições dos vários envolvidos são combinadas e passam a (pelo menos tentar) funcionar em conjunto. Há uma palestra em que Linus Torvalds resume isto muito bem em algumas poucas palavras: “branches são completamente inúteis, a não ser que você faça o merge”. Só que o versionamento seqüencial pára no branch e o merge precisa ser feito de forma manual porque o sistema não enxerga as dependências entre as alterações, só sabe qual delas aconteceu antes ou depois. Você pode conseguir ajuda para comparar duas versões do mesmo conteúdo, mas é você quem tem que saber quais devem ser as duas versões para este merge em particular que você quer fazer. A coisa fica ainda mais complicada quando você precisa importar modificações de outro branch periodicamente. Quando, por exemplo, seu projeto tem um branch estável em que só se faz conserto de bugs e outro — ou outros — em que são desenvolvidos novos recursos e que precisa receber as mesmas modificações que o estável para se manter livre de bugs. As pessoas acabam desenvolvendo algumas soluções arcanas para controlar manualmente quais alterações já foram aplicadas a quais linhas de desenvolvimento, coisa que o sistema de controle de versão podia (e deveria) fazer sem precisar de babá.

Os identificadores seqüenciais precisam ser atribuídos por algum tipo de servidor central. Não podem ser determinados por máquinas diferentes para evitar conflitos de nomes e acabam sendo completamente artificiais porque refletem simplesmente a ordem em que o servidor recebeu as alterações. Para a maioria das alterações, a ordem de aplicação pouco importa. E quando ela importa, os sistemas com versionamento seqüencial não costumam ajudar.

A alternativa ao controle seqüencial é o controle de patches (termo que admito estar inventando agora, então não deve ser muito preciso e provavelmente você vai encontrar isto com outro nome por aí). Um sistema de controle de patches associa um identificador a uma modificação, não ao resultado dela como os de controle seqüencial. Uma versão qualquer é simplesmente um acumulado de modificações que, quando combinadas, determinam um resultado final.

Sistemas seqüenciais são necessariamente centralizados. Não há como decidir qual o número para a próxima versão se não houver uma autoridade central para controlar a numeração. Porém, o inverso não é verdadeiro. Isto é, sistemas baseados em controle de patches não precisam ser distribuídos. A maioria dos que encontramos realmente é, mas não precisavam ser. A questão é que eles podem ser muito bem usados como sistemas centralizados, mas já tem tudo que precisam para serem distribuídos e independentes de um servidor central. Então porque se limitar? Além de poderem fazer tudo que os centralizados conseguem, os sistemas distribuídos ainda costumam ser muito mais fáceis de instalar. Se o Atwood tivesse escolhido um deles, não precisaria nem escrever um tutorial de instalação para si próprio. Era só escolher inicializar um diretório qualquer como repositório e começar a versionar o que quisesse. Sem serviços. Sem nomes de usuário. Sem senhas. Sem dores de cabeça com o firewall.

Minha impressão é que o funcionamento dos sistemas distribuídos de hoje é muito mais parecido com o modo como naturalmente pensamos do que o dos centralizados. Quando quisesse combinar duas linhas de trabalho distintas eu deveria apenas dizer a meu sistema que quero combinar as modificações que eu tenho com as que meu colega tem e ele deveria ser capaz de fazer isso sozinho (assumindo que não haja conflitos complicados). Eu não deveria precisar criar marcadores artificiais e manter manualmente um histórico de quais alterações dele eu já tenho. Por que algumas vezes ainda insistimos em usar sistemas centralizados? Há alguma vantagem oculta neles ou o quê?

Sobre grandes alterações pequenas

Qual é a granularidade das modificações que você envia ao seu sistema de controle de versão? Você se limita a modificações pequenas (que mudam o nome de uma varíavel ou outra, por exemplo) ou gosta das mas volumosas (como as que mudam o nome de uma rotina, introduzem um parâmetro novo e ainda corrigem um defeito em um pedaço do código que a chamava)?

Não é uma pergunta retórica. Pode responder. O espaço para comentários ali embaixo serve exatamente para isso.

Quando envio patches para algum projeto, tento torná-los tão pequenos e localizados quanto possível. Eu sei que não é uma experiência muito agradável revisar modificações monstruosas que afetam toda a base de código e tento poupar disso os mantenedores dos projetos para os quais colaboro. Eles são programadores, mas também são gente.

Não é tão raro que uma parte das modificações em um patch sejam aceitas e outras não. Caso o patch enviado seja muito extenso, o mantenedor vai ter um bocado de trabalho para separar as partes boas das ruins. Se ele tiver muitas outras preocupações no dia (o que não é nada incomum), pode ser que ele simplesmente recuse todo o patch, apesar de haver mudanças úteis, só para poupar o trabalho de filtragem. Para quem está contribuindo, certamente é melhor que as modificações sejam aceitas e um conjunto de patches pequenos torna as chances disso acontecer muito maiores do que um único patch grande. Nem todo patch que enviamos é aceito e precisamos conviver com isso. Modificações pequenas e localizadas ajudam muito quando algumas delas precisa ser rejeitadas.

Patches pequenos costumam ser mais ortogonais e fazem muito sentido no contexto colaborativo de um projeto de código aberto, principalmente com sistemas de controle de versão distribuídos. Mas ainda existem sistemas centralizados e — pior — cuja única solução para edição concorrente é usar travas para tentar tornar o desenvolvimento serializado e impedir toda e qualquer tentativa de paralelismo. Como se este cenário não fosse suficientemente ruim, alguns sistemas não suportam commits com múltiplas modificações e obrigam todo mundo a registrar alterações arquivo por arquivo, mesmo que toquem em muitos pontos. Para terminar imagine que a única forma de interação da equipe com a ferramenta de controle de versão seja um cliente gráfico com interface do século passado e dificilmente automatizável.

É serio mesmo, essas coisas ainda existem.

E tem gente que precisa usá-las.

Quando o mundo conspira contra você com tanta intensidade, fica mais difícil resistir à tentação de fazer uma modificação monstruosa com um comentário altamente descritivo como “Modificações do código”. Porém, mesmo quando estou em algum ambiente parecido com este, ainda tento fazer modificações pequenas. Não faço alterações tão pequenas como quando estou usando Darcs e vez ou outra combino duas ou três modificações em uma, mas sempre evito registrar uma alteração que resolva mais de um defeito ou melhoria.

Parece haver um certo conflito entre commits localizados e implementação de recursos. A renomeação de uma rotina, por exemplo, pode ser um dos passos que leva à inclusão de uma nova funcionalidade, mas com certeza não vai resolver a bronca sozinha. As funcionalidades precisam de modificações maiores, mas modificações menores são mais fáceis de entender, revisar e reverter. Será que este conflito real ou apenas um produto da nossa imaginação?

Controle de versão distribuído se distribui

Talvez eu simplesmente tenha pegado o trem tarde demais e estou vendo como novidade o que os veteranos veriam como normalidade, mas tem algo que venho observando há algum tempo. Não vou chamar de tendência para não arriscar queimar a língua no futuro, mas é no mínimo curioso.

Você tem notado como os sistemas de controle de versão distribuídos estão mais comuns?

Há um ano e meio nós no projeto EclipseFP estamos usando um sistema distribuído chamado Darcs. Ele vem se tornando um tipo de padrão de fato na comunidade Haskell porque é escrito em Haskell. Apesar do nosso projeto (ainda) não ser totalmente escrito em Haskell, em geral é uma boa idéia para um ambiente de desenvolvimento se distanciar o mínimo possível da sua comunidade. A escolha então foi óbvia.

Isto não prova muita coisa. Estou falando do meu próprio projeto, afinal de contas. Mas tem mais. A menos que você estivesse em algum tipo de caverna em um planeta distante da Terra durante os últimos dois anos, você já ouviu falar no Ubuntu. Eles também usam um sistema de controle de versão distribuído, o Bazaar-NG (também conhecido por bzr). Outro projeto do qual você talvez já tenha ouvido falar que usa controle de versão distribuído é o Linux. Eles usam o git, que foi originalmente escrito por Linus Torvalds para ser usado no desenvolvimento do seu kernel. Há até um espelho Darcs do repositório deles. Como se não bastasse há ainda os boatos sobre a versão 2.0 do Subversion (conhecido também pela alcunha svn) ser distribuída.

Mas o que me chamou a atenção foi um pequeno detalhe da decisão da Sun de abrir o código de Java semana passada. Passaria totalmente desapercebido se não fosse minha interminável lista de feeds e seu inesgotável estoque de artigos interessantes. Acontece que eles também vão passar a usar mais um sistema de controle de versão distribuído. O nome dele é Mercurial. hg para os íntimos.

A pergunta de um milhão de dólares (ou de um milhão de olhos, como vou tentar mostrar) é: por que eles estão fazendo isso?

Com controle de versão distribuído, cada cópia de trabalho é um repositório completo. Ou seja, cada desenvolvedor ou curioso que queira acompanhar o projeto tem o histórico de modificações ao código-fonte armazenado localmente. Há dez anos não era todo mundo que tinha 40GB de memória de armazenamento disponível e fazia sentido recorrer a um servidor central para armazenar o histórico de um projeto. Hoje as pessoas podem se dar ao luxo de armazenar o histórico do Linux (hoje com mais de 300MB) sem problemas.

Tecnicamente não é necessário haver algo como um repositório central quando se usa controle de versão distribuído. Não há uma hierarquia de repositórios, cada um é independente dos demais. Se eu e você estamos trabalhando em um mesmo projeto, podemos fazer modificações separadamente o tanto quanto quisermos e ambos terão o mesmo poder sobre seu repositório local. Ambos poderão gravar alterações, desfazê-las e consultar o histórico do projeto como quiserem sem precisar saber o que acontece com o repositório do outro. Se quisermos, podemos mesclar os dois repositórios para sincronizá-los e isso faz muito sentido se estivermos realmente trabalhando no mesmo projeto. Como esta operação costuma ser freqüente, as pessoas em geral elegem um dos repositórios como central. Elas passam a enviar suas alterações para lá quando estão prontas para publicação e consultá-lo quando precisam atualizar suas cópias locais. Mas só porque escolheram fazê-lo. Não há nenhuma limitação do sistema que as obrigue a fazer isso.

Isto facilita muito a vida tanto de quem quer contribuir com patches como de quem precisa gerenciá-los. Se você fez uma modificação no código de algum projeto, tudo que você precisa é fazer um mini-fork fazendo uma cópia local do repositório e gravar sua alteração. Mais tarde, quando o corpo de código principal for modificado, o sistema vai se encarregar de juntar tudo sozinho. Com controle centralizado você precisa convencer os mantenedores que o seu patch é importante para que eles o integrem ao código principal ou se contentar em refazer as modificações a cada revisão. Claro que você vai gravar seus patches localmente para facilitar, mas as coisas podem ficar bem cabeludas se você tiver muitas alterações.

Ter um repositório completo local significa também que você pode gravar modificações intermediárias. Todo mundo faz besteira de vez em quando e é bom poder saber que há algo mais que a função de desfazer do editor para te proteger. Quando podem as pessoas tendem a fazer modificações menores e detalhar melhor seus comentários. Ao mesmo tempo que o colaborador externo tem mais controle sobre suas alterações, o mantenedor ganha um histórico de versões mais limpo, livre de revisões gigantescas.

Toda a razão para disponibilizar seu código-fonte livremente é poder contar com a força de uma comunidade. Quanto mais gente vir o código, melhor ele vai poder ficar. Patches são um dos maiores desejos de qualquer mantenedor de um pedaço de código e controle de versão distribuído facilita a vida deles e de quem quer contribuir. Quando se quer atrair contribuições, diminuir as barreiras para quem quiser contribuir não faz mal a ninguém e é justamente isso que a Sun está tentando fazer agora com o OpenJDK.