Um Modelo Bem Sucedido para Ramificações no Git

O texto original em inglês[1], de Vincent Driessen, pode ser encontrado no endereço: http://nvie.com/posts/a-successful-git-branching-model/


Neste post, eu apresento o modelo de desenvolvimento que introduzi em alguns projetos (no trabalho e privados) há cerca de um ano, e que acabou se mostrando bem sucedido. Gostaria de ter escrito há mais tempo, mas não achei um tempo até agora. Não vou falar dos detalhes dos projetos, somente sobre a estratégia de ramificações e o gerenciamento de lançamentos.

Git Model

Imagem também disponível em PDF: http://nvie.com/files/Git-branching-model.pdf (Licença: CC BY-SA)

Ela gira em torno do Git como ferramenta para versionamento de todo o nosso código-fonte.

Por que Git?

Para uma discussão aprofundada dos prós e contras do Git em comparação com sistemas de controle de código-fonte centralizados, veja a web. Há várias brigas acontecendo[2]. Como desenvolvedor, prefiro o Git sobre todas as demais ferramentas. Ele realmente mudou a forma como desenvolvedores pensam em mesclagens e ramificações [3]. Do mundo clássico do CVS/Subversion que eu vim, mesclagens/ramificações são consideradas um pouco assustadores (“cuidado com conflitos na mesclagem, eles mordem”) e algo que você faz apenas de vez em quando.

Mas, com o Git, essas ações são extremamente baratas e simples, e são consideradas partes principais do fluxo de trabalho diário. Por exemplo, nos livros de CVS/Subversion, ramificações e mesclagens são discutidos nos últimos capítulos (para usuários avançados), enquanto que em todos os livros de Git, já são tratados no capítulo 3 (conceitos básicos).

Como consequência da simplicidade e da natureza repetitiva, você não precisa mais ter medo. As ferramentas ajudam nas mesclagens e ramificações mais que com outras coisas.

Já chega de ferramentas, vamos para o modelo de desenvolvimento. O modelo que vou apresentar não é mais que um conjunto de procedimentos que todos os membros da equipe devem seguir em ordem para chegar a um processo de desenvolvimento de software bem gerenciado.

Decentralizado, mas Centralizado

A configuração de repositório que usamos e que funciona bem com esse modelo é com um repositório central “da verdade”. Note que o repositório só é considerado o central (pois o Git é um DVCS, não existe algo como “repositório central” tecnicamente falando.) Vamos nos referir a esse repositório como origin, já que é um termo comum para os usuários de Git.

Cada desenvolvedor puxa e envia para o origin. Mas além das relações com o origin, cada desenvolvedor também pode puxar mudanças de outros colegas para formar subequipes. Por exemplo, pode ser útil trabalhar junto com dois ou mais desenvolvedores em uma grande nova funcionalidade, antes de enviar o trabalho em progresso para o origin prematuramente.

Na figura acima, há subequipes de Alice e Bob, Alice e David, e Clair e David. Tecnicamente, significa que Alice definiu um Git remoto chamado “bob”, apontando para o repositório do Bob e vice-versa.

As Ramificações Principais

Em essência, o modelo é fortemente inspirado por modelos existentes por aí. O repositório central tem dois ramos principais, com linhas do tempo infinitas[4]:

  • master
  • develop

O ramo master no origin deve ser familiar para qualquer usuário do Git. O ramo develop existe em paralelo ao ramo master.

Consideramos o origin/master o ramo principal onde o código do HEAD sempre reflete um estado pronto para produção.

Consideramos o origin/develop o ramo onde o código do HEAD sempre reflete um estado com as últimas mudanças de desenvolvimento entregues para o próximo lançamento. Alguns chamariam de “ramo de integração”. É daqui que os builds noturnos são construídos.

Quando o código fonte do develop alcança um ponto estável e está pronto para ser lançado, todas as mudanças devem ser mescladas no master de alguma forma, e depois etiquetadas com o número do lançamento. Os detalhes serão apresentados mais à frente.

Então, cada vez que as mudanças são mescladas de volta no master, elas serão um novo lançamento por definição. Tentamos ser bem rigorosos com isso, para que possamos usar um script no Git para construir e lançar automaticamente o software para os servidores em produção, toda vez que houver um envio para o master.

Ramos de Apoio

Nosso modelo de desenvolvimento também inclui uma variedade de ramos de apoio para ajudar no desenvolvimento em paralelo entre os membros da equipe, facilitar o rastreamento das funcionalidades, preparar para lançamentos, e ajudar a fazer correções rápidas nos problemas do produto em produção. Diferente dos ramos principais, essas sempre têm um tempo de vida limitado, pois serão removidas eventualmente.

Os tipos de ramificações que podemos fazer são:

  • Ramificações de funcionalidades
  • Ramificações de lançamentos
  • Ramificações de correções rápidas (hotfix)

Cada uma dessas ramificações tem um propósito específico e são ligadas a regras rigorosas sobre de quais ramos podem se originar e para quais ramos serão mescladas.

De nenhuma forma essas ramificações são “especiais” de uma perspectiva técnica. Os tipos são categorizados de acordo com como nós as usamos. Formam apenas ramos comuns do Git.

Ramificações de Funcionalidade

Pode se ramificar de:
develop

Deve mesclar para:
develop

Convenção de nome do ramo:
qualquer um, exceto master, develop, lancamento-* ou correcao-*

Ramificações de funcionalidades (ou, às vezes, chamados de ramificações de tópico) são realizadas para desenvolver novas funcionalidades para um próximo lançamento ou um lançamento de um futuro distante. Ao começar o desenvolvimento de uma funcionalidade, o alvo de lançamento ainda pode ser desconhecido. A essência do ramo criado é que ele existe enquanto a funcionalidade estiver em desenvolvimento, mas vai eventualmente ser mesclado no develop (para definitivamente adicionar a nova funcionalidade ao novo lançamento) ou descartado (no caso de um experimento frustrado.)

As ramificações de funcionalidade geralmente são feitas apenas nos repositórios de desenvolvedor, não no origin.

Criando um ramo de funcionalidade

Ao iniciar o trabalho em uma nova funcionalidade, ramifique do develop:

$ git checkout -b minhaFuncionalidade develop
Switched to a new branch "minhaFuncionalidade"

Incorporando o ramo de funcionalidade ao develop

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff minhaFuncionalidade
Updating ea1b82a..05e9557
(Resumo das alterações)
$ git branch -d minhaFuncionalidade
Deleted branch minhaFuncionalidade (was 05e9557).
$ git push origin develop

O flag --no-ff faz com que a mesclagem crie um novo objeto de commit, mesmo que a mesclagem pudesse ter sido feita com um fast-forward. Isso evita perder a informação histórica sobre a existência do ramo de funcionalidade, e agrupa todos os commits que fizeram parte da funcionalidade adicionada. Compare:

No segundo caso, é impossível ver pela história do Git quais dos objetos de commits que, juntos, implementaram a funcionalidade – você teria que ler manualmente todas as mensagens de log. Reverter toda a funcionalidade (ou seja, um grupo de commits) é uma dor de cabeça no segundo caso, enquanto é feito com facilidade quando o flag --no-ff é usado.

Sim, vai criar mais alguns objetos vazios de commit, mas o ganho é muito maior que a perda.

Ramificações de Lançamentos

Pode se ramificar de:
develop

Deve mesclar para:
develop e master

Convenção de nome do ramo:
lancamento-*

Ramificações de lançamentos apoiam a preparação para novos lançamentos para produção. Elas permitem colocar os pingos nos is e cortar os ts. Além disso, permitem pequenas correções e preparações de metadados para o lançamento (número de versão, data de compilação, etc.) Ao fazer esse trabalho em um ramo de lançamento, o ramo develop fica livre para receber funcionalidades para o próximo lançamento.

O momento chave para ramificar para lançamento é quando o develop (quase) reflete o estado desejado do novo lançamento. Ao menos todas as funcionalidades visadas para o lançamento já devem ter sido mescladas no develop. Todas as funcionalidades para um lançamento futuro não podem ser mescladas ainda – precisam esperar até após a ramificação de lançamento ser feita.

É logo após a ramificação de lançamento que o software recebe o número de versão – nunca antes. Antes desse momento, o ramo develop refletia mudanças para o próximo lançamento, mas não estava claro se o “próximo lançamento” iria ser a 0.3 ou 1.0, até o início dessa ramificação. A decisão é feita no início da ramificação de lançamento e é definida pelas regras do projeto sobre numeração de versão.

Iniciando a ramificação de lançamento

É criado do ramo develop. Vamos dizer, por exemplo, que a versão atual em produção é 1.1.5 e temos um próximo lançamento chegando. O develop está pronto para esse lançamento, e decidimos que a versão será 1.2.0 (em vez de 1.1.6 ou 2.0.) Então, ramificamos e damos o nome com o número da versão:

$ git checkout -b lancamento-1.2 develop
Switched to a new branch "lancamento-1.2"
$ ./atualiza-versao.sh 1.2
Arquivos modificados, versao atualizada para 1.2.
$ git commit -a -m "Atualiza versão para 1.2"
[lancamento-1.2 74d9424] Atualiza versão para 1.2
1 files changed, 1 insertions(+), 1 deletions(-)

Depois de criar e trocar de ramo, ajustamos o número da versão. Aqui, atualiza-versao.sh é um script shell que altera alguns arquivos localmente para ajustar a nova versão. (Poderia ser uma mudança manual – o ponto é que alguns arquivos mudam.) Depois, a versão atualizada é enviada.

Esse novo ramo pode existir por algum tempo, até que a versão seja lançada definitivamente. Durante esse tempo, correções podem ser aplicadas nesse ramo (em vez de no develop.) Adicionar novas funcionalidades é estritamente proibido. Elas devem ser mescladas ao develop e esperar pelo próximo lançamento.

Finalizando o ramo de lançamento

Quando o estado do ramo de lançamento está pronto para se tornar um lançamento de verdade, algumas ações precisam ser feitas. Primeiro, o ramo de lançamento é mesclado ao master (já que todo commit para o master é um novo lançamento por definição.) Depois, o commit do master precisa receber uma etiqueta para referência futura a essa versão. Finalmente, as mudanças do ramo de lançamento precisam ser mescladas de volta para o develop, para que as versões futuras também contenham as correções do ramo.

Os dois primeiros passos no Git:

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff lancamento-1.2
Merge made by recursive.
(Resumo das alterações)
$ git tag -a 1.2

O lançamento está feito, e etiquetado para referência futura.

Você também pode usar as flags -s ou -u <chave> para assinar a etiqueta criptograficamente.

Para guardar as mudanças feitas no ramo de lançamento, precisamos mesclá-las de volta no develop. No Git:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff lancamento-1.2
Merge made by recursive.
(Resumo das alterações)

Esse passo pode gerar algum conflito (provavelmente vai, já que mudamos pelo menos o número de versão.) Se gerar, conserte e faça o commit.

Agora terminamos, e o ramo de lançamento pode ser removido, já que não precisamos mais dele.

$ git branch -d lancamento-1.2
Deleted branch lancamento-1.2 (was ff452fe).

Ramificações de Correções Rápidas (hotfix)

Pode se ramificar de:
master

Deve mesclar para:
develop e master

Convenção de nome do ramo:
correcao-*

Ramificações de correções rápidas são bem parecidas com as correções de lançamento no que diz respeito a preparar para um novo lançamento para produção, mas são ramificações não-planejadas. São feitas a partir da necessidade de agir imediatamente por causa de um estado indesejado de uma versão em produção. Quando um bug crítico na produção precisa ser resolvido imediatamente, a ramificação de correção é feita a partir da etiqueta do ramo master que corresponde à versão em produção.

O essencial é que o trabalho dos colegas de equipe pode continuar (no ramo develop,) enquanto outra pessoa prepara a correção na produção.

Iniciando a ramificação de correção rápida

As ramificações para correções rápidas são feitas à partir do ramo master. Digamos que, por exemplo, a 1.2 é a versão atual rodando em produção e causando problemas devido a um bug. Mudanças no develop ainda estão instáveis. Podemos criar um ramo de correção e começar a resolver o problema:

$ git checkout -b correcao-1.2.1 master
Switched to a new branch "correcao-1.2.1"
$ ./atualiza-versao.sh 1.2.1
Arquivos modificados, versao atualizada para 1.2.1.
$ git commit -a -m "Atualiza versão para 1.2.1"
[correcao-1.2.1 41e61bb] Atualiza versão para 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)

Não se esqueça de atualizar a versão após a ramificação.

Então, corrija o defeito e envie as correções em um ou mais commits:

$ git commit -m "Corrige problema sério em produção"
[correcao-1.2.1 abbe5d6] Corrige problema sério em produção
5 files changed, 32 insertions(+), 17 deletions(-)

Finalizando o ramo de correção

Ao terminar, a correção precisa ser mesclada de volta no master, e também no develop, garantindo que a correção esteja nas próximas versões também. É bem parecido com a finalização dos ramos de lançamento.

Primeiro, atualize o master e etiquete o lançamento:

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff correcao-1.2.1
Merge made by recursive.
(Resumo das alterações)
$ git tag -a 1.2.1

Você também pode usar as flags -s ou -u <chave> para assinar a etiqueta criptograficamente.

Depois, inclua a correção no develop:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff correcao-1.2.1
Merge made by recursive.
(Resumo das alterações)

A única exceção a essa regra é quando o ramo de lançamento já existe, e o ramo de lançamento deve ser mesclado no ramo de lançamento, não no develop. Mesclar a correção no ramo de lançamento vai eventualmente levar a correção a ser mesclada no develop também, quando o ramo de lançamento for finalizado. (Se o trabalho no develop exigir essa correção imediatamente, não pode esperar o fim do ramo de lançamento, você pode mesclar o ramo no develop também.)

Finalmente, remova o ramo temporário:

$ git branch -d correcao-1.2.1
Deleted branch correcao-1.2.1 (was abbe5d6).

Conclusão

Mesmo não havendo nada novo nesse modelo de ramificação, ele foi bastante útil nos nossos projetos. Ele forma um modelo mental elegante que é fácil de compreender e permite aos colegas de equipe desenvolver um entendimento compartilhado dos processos de ramificação e lançamento.

Um PDF de alta qualidade está disponível aqui. Vá em frente e pendure-o na parede para dar uma olhada rápida a qualquer momento.

Git-branching-model.pdf

E para quem pediu, aqui está o gitflow-model.src.key do diagrama principal (Apple Keynote).


Notas do tradutor:

[1] O texto é relativamente velho, mas ainda serve de exemplo para quem está aprendendo Git. Caso tenha sugestões de textos mais atualizados, por favor, informe nos comentários!

[2] As brigas eram da época do texto, 2009/2010.

[3] É complicado traduzir um texto e usar as palavras do português em vez das que usamos no dia-a-dia, em inglês. Mas acho que algumas palavras traduzidas trazem mais significado para os brasileiros/portugueses/angolanos/etc. Espero que me perdoem, e que aprendam inglês para ler o texto original.

[4] Encontrei por aí outros pares bem comuns: “master/staging” e “production/development”

Anúncios
Marcado com:
Publicado em Git

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

w

Conectando a %s

%d blogueiros gostam disto: