Git ou SVN? Como a Nuance Healthcare optou por um modelo de branch do Git

Matt Shelton
Matt Shelton
Voltar para a lista

Este é um post convidado de Matt Shelton da Nuance Healthcare. Este é o primeiro post de uma série sobre sua equipe que fala da mudança do Subversion para o Git, porque fizeram isso e o que encontraram ao longo do caminho. Matt também fala sobre isso no Atlassian Summit 2015. Esta série vai retratar tudo o que ele não conseguiu dizer em sua palestra de 30 minutos, com mais contexto.

Histórico

Minha equipe faz parte da divisão de saúde da Nuance. Nossa localização consiste em alguns escritórios e residências na costa leste dos EUA e em um escritório em Pune. A gente desenvolve serviços web em Java para fornecer soluções de PLN[1] para o mercado de saúde.

Na maioria das vezes, quem utiliza esses serviços são outras empresas de software de saúde (incluindo a gente mesmo), como fornecedores de EHR e empresas de dados de análise de saúde. É realizada a venda, de modo direto, de alguns produtos para hospitais, e os usuários finais dos aplicativos variam de médicos a equipes de faturamento médico. Pessoas "normais" como você e eu nunca tocam no software que minha equipe desenvolve.

Nossa equipe já trabalhou algumas vezes com combinações de produtos de gerenciamento do ciclo de vida de aplicativos. O primeiro foi a mistura do Rally Enterprise e Seapine TestTrack Pro, finalizado após cerca de 14 meses de trabalho duro com o Rational Team Concert e, de forma gradual, a gente migrou por completo para o Atlassian Stack (Jira, Confluence, Bamboo, Crucible, e Bitbucket). Desde sempre a gente usa o Subversion 1.4/1.5 e o SCM com uma estrutura quasi-normal de troncos/ramificações/marcações. O Maven também é usado há muito tempo para gerenciar os projetos de build e dependências, e mudamos do Jenkins para o Bamboo para integração contínua (IC) há algum tempo, a fim de utilizar integrações mais rígidas com o Jira e os recursos flexíveis de criação e implementação de agentes. Tudo o que a gente usa (agora) está atrás do firewall por alguns motivos[2].

Git ou SVN?

Oferecemos suporte a cerca de dez produtos individuais em quatro famílias de produtos, e os proprietários desses produtos estão sempre lutando por priorização e tempo. É bom que nosso trabalho esteja em alta demanda, e isso não é de forma alguma uma reclamação, mas ele também exige a redução de lançamentos em um ritmo incomum e a necessidade de mudar de direção no meio de um sprint[3].

De fato, nosso processo de desenvolvimento parecia proibitivo às vezes. Minha equipe conversava com frequência mais ou menos o seguinte:

Eu: o 1.8.0 precisa ser lançado à garantia de qualidade agora para os testes de regressão, para que o cliente foo possa ir para a versão beta na próxima semana. Desenvolvedor: ainda estou trabalhando no ABC-123 que está no tronco. Não está pronto. Eu: o foo não precisa do ABC-123. Ele pode ser adicionado na próxima versão. Desenvolvedor: mas tenho trabalhado nisso por semanas. Não há um ponto claro a ramificar para cortar uma versão. Eu: bom, você vai precisar desfazer todas as suas alterações manualmente. Você tem cerca de duas horas ou a garantia de qualidade não vai conseguir finalizar a tempo.

Eu sei, pareço um idiota! Eu nunca quis ser, e é claro que estou exagerando um pouco para me fazer entender, porém, a gente teve mesmo que descobrir como obter o código que estava, de modo provisório, em um outro lugar para que fosse possível cortar uma versão, e depois colocar ela de volta na versão seguinte[4]. E isso acontecia o tempo todo.

Agora, sei que alguns de vocês estão pensando "o Subversion suporta ramificações, Matt... ". Com certeza, e elas são usadas em alguns casos com o SVN 1.4 e 1.5. A ramificação é uma operação tranquila no SVN; o merge pode ser uma chatice. À medida que o SVN amadureceu, ficou melhor, com certeza. Mas a gente sabia que havia opções melhores para nós, então, quando surgiu a pergunta sobre escolher entre o SCN ou o Git, a gente optou pelo Git.

Observação: a gente fez uma análise breve do SVN mais recente (1.8 na época) para ver se ele era robusto o suficiente para resolver nossos problemas, mas não ficamos muito satisfeitos. Um de nossos grupos tem uma grande configuração do Perforce e ele tinha muito do que a gente precisava, mas eu não tinha como arcar com os custos de licenciamento. A gente também considerou o Mercurial por um momento, mas no final, a exposição da equipe existente ao Git foi suficiente para confirmar que ele era a opção certa.

Não vou dourar a pílula: as ferramentas da Atlassian ajudam de verdade as equipes que usam o Git. Outros SCMs funcionam bem; nossa integração com o SVN foi satisfatória, pois nos vinculou ao local onde as alterações de uma determinada história do usuário foram feitas. No entanto, os recursos de integração para equipes que usam o Bitbucket Server[5] são mais robustos e mais naturais na interface e na experiência de desenvolvimento do Jira Software – o mesmo acontece com o Bamboo.

Sabendo disso e tendo visto algumas demonstrações espetaculares na Summit 2013, incentivei bastante a equipe a fazer a mudança. Ninguém se opôs, e a gente já tinha as licenças para isso.

Como escolher um modelo de ramificação do Git

Depois de optar pela mudança, nosso primeiro desafio foi decidir qual modelo de ramificação do Git implementar em nossa equipe. O microsite de Git da Atlassian, bem como esta excelente apresentação da Summit 2013, explicam com mais detalhes o que é um modelo de ramificação. De forma resumida, descreve como usar ramificações no Git para potencializar seu fluxo de trabalho de desenvolvimento.

No SVN, a gente tinha um modelo de ramificação que vou chamar de "crie uma quando perceber que – Aí Meu Deus! – precisa de uma":

  • O código mais recente está no tronco. As versões do tronco vão ser numeradas A.B.0- {build}
  • Se for necessária a correção de uma versão baseada em tronco (por exemplo, um bug em 1.2.0-64), uma ramificação é criada e a partir daí a gente vai lançar versões A.B.C-{build}, onde C muda a cada versão nova. Essas ramificações podem nunca existir em uma determinada A.B e a gente pode até ter mais de uma.
  • Todas as versões recebem marcações em um diretório específico para esse fim.

Um aparte sobre as versões: muitos anos atrás, quando eu estava apenas começando a gerenciar uma equipe de desenvolvimento, nosso engenheiro de versão tinha um sistema de versões que era... como devo dizer?... muito pouco intuitivo. Em resumo, cada versão era uma correção da anterior (A.B.n), que não respeitava o local onde a correção foi criada. Descobrir os locais de origem e, em quase todos os casos, a ordem da versão, exigia a consulta ao svn log. A gente imprimiu a árvore em uma parede para referência. Além disso, nossos números de versão disponibilizados ao público tendem a ser algo como 3.0, 3.1, 3.5, 4.0 ou algo que o cliente pode imaginar. Tenha em mente, porém, que minha equipe cria serviços web e não um produto em uma caixa. Nossas APIs são um contrato. Alguns anos atrás, estabeleci que as builds da minha equipe e, portanto, suas versões, iam seguir as regras de versão semântica. Tive que defender esse posicionamento algumas vezes com a alta gerência, mas agora ficou clara a importância das regras e não se fala mais nisso. Os parceiros apreciam esse tipo de clareza.

Mencionei acima um problema, onde havia uma versão em desenvolvimento (digamos 1.2.0) e um recurso, cuja data de lançamento se aproximava, que ainda não estava pronto. Era preciso extrair o código, cortar a versão, ramificar para branches/1.2.1 e depois fazer o merge desse código novamente, na esperança de que não houvesse uma falha no disco rígido nesse meio tempo[6].

Remover um recurso inteiro por si só de um tronco compartilhado dá trabalho. Todos odiavam ter que fazer isso. O svn blame pode ser útil, assim como uma ferramenta de comparação robusta, mas ainda é muito chato trabalhar com ele. Muitas vezes levei para o lado pessoal, pensando que o meu mau planejamento foi o responsável por não ter tudo pronto antes do lançamento[7]. Minha equipe lidou com isso por tempo suficiente.

Às vezes, a gente fazia inúmeras correções para evitar problemas e pedia aos desenvolvedores que não mexessem em nada (um congelamento de código virtual, se você preferir), só para não poluir o tronco antes de um lançamento.

Portanto, a gente sabia que precisava, ao menos, de ramificações de recursos. Há um modelo de ramificação simples do Git que é aplicável: uma ramificação principal para o que está no ambiente de produção, e usando ramificações de recursos para cada recurso, bug, etc., a equipe tem que gerenciar a ordem de merge para garantir que o que vai ser lançado na principal é o que deve ser enviado para o lançamento. Isso é, em resumo, a mesma coisa que tínhamos antes, com um melhor isolamento de recursos, mas queríamos liberdade com nosso poder.

Em nosso ambiente, muitas vezes é preciso manter algumas versões em produção e, talvez, corrigir defeitos em uma versão que tem de 2 a 3 revisões menores mais antigas do que a que estamos trabalhando no momento. Portanto, além das ramificações de recursos, também era necessário algum tipo de ramificação de versão ou similar que permitisse corrigir problemas de versões anteriores. A equipe do Atlassian Bitbucket Server faz isso. Eles fazem correções em ramificações de suporte de longa duração e, em seguida, fazem o merge delas no fluxo de ramificação para que uma correção chegue a todos os fluxos de suporte.

O modelo deles parecia muito bom, e realizamos algumas interações de protótipo com esse modelo para ver se ele atendia às nossas necessidades. O "aplicativo excepcional" para eles é o merge contínuo de uma correção até o ramo de desenvolvimento. Embora tenhamos gostado desse conceito, em todos os testes a gente se deparou com um item ou outro em nossas dependências do Maven. Além disso, como regra, não era possível garantir o merge direto do trabalho de uma versão para outra. Em alguns casos, era preciso implementar a mesma correção de maneiras um pouco diferentes entre as versões, portanto, um merge direto não era possível.

Alguns membros da equipe têm preferência por uma variação desse modelo conhecida como "git-flow". O Git-flow é um conjunto de convenções de nomenclatura de ramificação e diretrizes de merge, de autoria de Vincent Driessen. Ele pareceu muito natural para a equipe e a estrutura agradou a todos, pois eliminou muitas das perguntas sobre "o que eu faço quando for preciso fazer x?". Em geral, as respostas eram muito óbvias. Em vez de explicar o que é o git-flow, você pode saber mais sobre isso lendo o tutorial da Atlassian.

A única lacuna deixada pelo o git-flow foi o que fazer com os lançamentos de longa duração em produção. Como a principal continua avançando, não foi possível usar o fluxo de trabalho de hotfix do git-flow para uma correção de bug de uma versão anterior. Por outro lado, nem sempre queríamos uma ramificação de suporte.

Na maioria das vezes, um hotfix que corrige apenas a versão mais recente em produção deve ser suficiente; o suporte só existe quando é preciso voltar mais longe ou manter a compatibilidade por um motivo ou outro. Nesse último caso de uso, uma análise detalhada foi feita e criamos critérios para usar uma ramificação de suporte em vez de um hotfix e o upgrade de versão secundária:

  1. Não é possível fazer novamente o merge desse código de modo trivial no desenvolvimento.
  2. O parceiro/cliente não pode lidar com uma alteração de interface que vem com a versão mais recente.
  3. Há uma dependência interna que não pode ser alterada.[8]

Ambos os pacotes de extensão do git-flow[9] fornecem suporte ao conceito de ramificação de suporte, que não faz parte do projeto original do git-flow, mas se tornou popular o suficiente para garantir sua inclusão.

O Git-flow ofereceu um fluxo de trabalho que gostamos, com o suporte de ferramentas que a gente precisava. No próximo post, vou falar sobre o que aconteceu ao tentar esse fluxo em um projeto de POC usado para representar nosso processo de desenvolvimento. Foi... uma experiência de aprendizado!

[1]: Processamento de linguagem natural. PODEMOS LER SEUS PENSAMENTOS. (Não. Na verdade, não.)

[2]: Há muita coisa atraente nas ofertas de nuvem da Atlassian, mas, por enquanto, a gente precisa se concentrar nos nossos servidores e dados. Embora a gente não precise fazer muito com dados de PHI, nosso software precisa e o manter o mais seguro possível é importante.

[3]: Shhhh... não conte a Ken Schwaber.

[4]: O que pode ter acontecido apenas alguns dias depois de qualquer maneira.

[5]: Antes conhecido como Stash. Olá, Rebranding da Atlassian!

[6]: Sei que sempre é possível extrair esse código do commit anterior. Brincadeira.

[7]: Em geral, não era esse o caso – muitas vezes, o que acontecia era que o prazo de outra pessoa mudava e era preciso que reagir com rapidez.

[8]: Essa é uma daquelas coisas que não posso falar em meu próprio blog. Só confie em mim. "Motivos".

[9]: O pacote original de Vincent Driessen não é mais atualizado. No entanto, uma nova bifurcação é atualizada com frequência.

Pronto(a) para aprender Git?

Tente este tutorial interativo.

Comece agora mesmo