Articles

Um modelo de sucesso de Git branching

Posted on

Nota de reflexão (5 de Março de 2020)

Este modelo foi concebido em 2010, agora há mais de 10 anos atrás, e não verylong depois da criação de Git em si. Nesses 10 anos, o git-flow (o modelo de ramificação apresentado neste artigo) tornou-se extremamente popular na equipa de software de manya ao ponto de as pessoas terem começado a tratá-lo como uma espécie de padrão – mas infelizmente também como um dogma ou panaceia.

Durante esses 10 anos, o próprio Git tomou o mundo por uma tempestade, e o tipo de software mais popular que está a ser desenvolvido com Git está a mudar para aplicações web – pelo menos na minha bolha de filtragem. As aplicações web são tipicamente entregues de forma contínua, não roladas para trás, e não é preciso apoiar versões múltiplas do software a correr na natureza.

Esta não é a classe de software que eu tinha em mente quando escrevi o blogpósto há 10 anos atrás. Se a sua equipa está a fazer a entrega contínua de software, sugiro que adopte um fluxo de trabalho muito mais simples (como o GitHubflow) em vez de tentar o git-flow dooshoehorn na sua equipa.

Se, no entanto, estiver a construir software explicitamente versionado, ou se precisar de suportar múltiplas versões do seu software no wild, o thengit-flow pode ainda ser tão bom para a sua equipa como tem sido para as pessoas nos últimos 10 anos. Nesse caso, leia on.

Para concluir, lembre-se sempre que as panaceias não existem. Considere o seu próprio texto. Não seja odioso. Decida por si.

Neste post, apresento o modelo de desenvolvimento que introduzi para alguns dos meus projectos (tanto no trabalho como privados) há cerca de um ano, e que se revelou muito bem sucedido. Já há algum tempo que queria escrever sobre isso, mas nunca encontrei tempo para o fazer de forma exaustiva, até agora. Não vou falar sobre nenhum dos detalhes dos projectos, apenas sobre a estratégia de ramificação e gestão de lançamento.

Porquê idiotice? ¶

Para uma discussão aprofundada sobre os prós e contras de Git em comparação com os sistemas de controlo de código de fonte centralizado, ver theweb. Há ali muitos flamewares. Como programador, prefiro Git acima de todas as outras ferramentas existentes hoje em dia. Git mudou realmente a forma como os programadores pensam em fundir e ramificar. Do mundo clássico CVS/Subversão de onde vim, fundir/brancar sempre foi considerado um pouco assustador (“cuidado com os conflitos de fusão, eles mordem-te!”) e algo que só se faz de vez em quando.

Mas com Git, estas acções são extremamente baratas e simples, e sãoconsideradas como uma das partes centrais do seu fluxo de trabalho diário, na verdade. Por exemplo, nos livros CVS/Subversion, a ramificação e a fusão são discutidas pela primeira vez nos últimos capítulos (para utilizadores avançados), enquanto que em todo o Gitbook, já é abordada no capítulo3 (noções básicas).

Como consequência da sua simplicidade e natureza repetitiva, a ramificação e a fusão já não são algo a temer. As ferramentas de controlo de versões são supostamente mais do que qualquer outra coisa para ajudar na ramificação/fusão.

Bastante sobre as ferramentas, vamos avançar para o modelo de desenvolvimento. O modelo que vou apresentar aqui não é essencialmente mais do que um conjunto de procedimentos que qualquer membro da equipa tem de seguir para chegar a um processo de desenvolvimento gerido por software.

Descentralizado mas centralizado ¶

A configuração do repositório que usamos e que funciona bem com este modelo de ramificação, é que com um repositório central de “verdade”. Note-se que este repo só é considerado central (uma vez que Git é um DVCS, não existe um repo central a um nível técnico). Referimo-nos a este repo como origin, uma vez que este nome é familiar a todos os utilizadores de Git.

Cada desenvolvedor puxa e empurra para a origem. Mas para além das relações centralizadas de empurrar-puxar, cada revelador pode também puxar mudanças de outros pares para formar sub equipas. Por exemplo, isto pode ser útil para trabalhar em conjunto com dois ou mais programadores numa grande novidade, antes de empurrar o trabalho em curso paraorigin prematuramente. Na figura acima, existem subequipas de Alice e Bob,Alice e David, e Clair e David.

Tecnicamente, isto significa nada mais do que que que Alice definiu um comando Git, chamado bob, apontando para o repositório do Bob, e vice-versa.

Os ramos principais ¶

No núcleo, o modelo de desenvolvimento é grandemente inspirado pelos modelos existentes no exterior. O repo central comporta dois ramos principais com uma vida útil infinita:

  • master
  • develop

O master ramo em origin deve ser familiar a todos os utilizadores de Git. Paralelamente ao ramo master, existe outro ramo chamado develop.

p> Consideramosorigin/mastercomo sendo o ramo principal onde o código fonte deHEADreflecte sempre um estado de prontidão para produção.

Consideramos origin/develop como sendo o ramo principal onde o código fonte deHEAD reflecte sempre um estado com as últimas alterações de desenvolvimento entregues para o próximo lançamento. Alguns chamariam a isto o “ramo de integração”. É aqui que qualquer construção nocturna automática é construída de.

quando o código fonte no ramo develop atinge um ponto estável e está pronto para ser lançado, todas as alterações devem ser fundidas novamente em master de alguma forma e depois etiquetadas com um número de lançamento. Como isto é feito em detalhe será discutido mais adiante em.

Por isso, cada vez que as alterações são fundidas de volta em master, esta é, por definição, uma nova versão de produção. Tendemos a ser muito rigorosos nisto, para que teoricamente, pudéssemos usar um script de gancho Git para construir automaticamente e distribuir o nosso software para os nossos servidores de produção sempre que houvesse um commit emmaster.

Apoio a ramos ¶

P>Próximo aos ramos principais master e develop, o nosso modelo de desenvolvimento usesa variedade de ramos de apoio para ajudar ao desenvolvimento paralelo entre os membros da equipa, facilitar o seguimento de características, preparar para lançamentos de produção e ajudar a resolver rapidamente problemas de produção ao vivo. Ao contrário dos ramos principais, estes ramos têm sempre um tempo de vida limitado, uma vez que serão removidoseventualmente.

Os diferentes tipos de ramos que podemos utilizar são:

  • Ramos de características
  • Ramos de características
  • Ramos de características quentes

Cada um destes ramos tem um objectivo específico e estão vinculados a regras estritas quanto a que ramos podem ser o seu ramo de origem e quais os ramos que devem ser os seus alvos de fusão. Passaremos por eles num minuto.

Estes ramos não são, de modo algum, “especiais” de uma perspectiva técnica. Os tipos de ramos são categorizados pela forma como os utilizamos. São, evidentemente, ramos simples e antigos.

Ramos de feitura ¶

Podem ramificar-se de:developDevem fundir-se de novo:developConvenção de nomenclatura de sucursais: qualquer coisa exceptomasterdeveloprelease-*, ouhotfix-*

Agências de características (ou por vezes chamadas ramos tópicos) são utilizadas para desenvolver novas características para o lançamento futuro próximo ou distante. Quando se inicia o desenvolvimento de uma funcionalidade, o lançamento alvo em que esta funcionalidade será incorporada pode muito bem ser desconhecido nesse momento. A essência de uma característica é que ela existe enquanto a característica estiver em desenvolvimento, mas acabará por ser fundida novamente em develop (para adicionar definitivamente a nova característica à próxima versão) ou descartada (no caso de uma experiência decepcionante).

Os ramos de funcionalidade normalmente existem apenas nos repos do programador, não em origin.

Criar um ramo de funcionalidade ¶

Quando se começa a trabalhar numa nova funcionalidade, ramifica-se a partir do develop ramo.

$ git checkout -b myfeature developSwitched to a new branch "myfeature"

Incorporating a finished feature on develop ¶

Finished features may be merged into the develop branch to definitely addthem to the upcoming release:

$ git checkout developSwitched to branch 'develop'$ git merge --no-ff myfeatureUpdating ea1b82a..05e9557(Summary of changes)$ git branch -d myfeatureDeleted branch myfeature (was 05e9557).$ git push origin develop

O --no-ff bandeira faz com que a fusão crie sempre um novo objecto de compromisso, mesmo que a fusão possa ser realizada com um avanço rápido. Isto evita a perda de informação sobre a existência histórica de um ramo de característica e grupos – todos os commits que juntos adicionaram a característica. Compare:

Neste último caso, é impossível ver a partir do histórico Git quais dos objectos de commit em conjunto implementaram uma funcionalidade – teria de ler manualmente todas as mensagens de registo. Reverter toda uma funcionalidade (ou seja, um grupo de commits),é uma verdadeira dor de cabeça nesta última situação, enquanto que é fácil de fazer se o--no-ff flag foi utilizado.

Sim, irá criar mais alguns (vazios) objectos de commit, mas o ganho é muito maior do que o custo.

Largar ramos ¶

Pode ramificar-se de:developDeve fundir-se de novo:developemasterConvenção de nomeação de ramos:release-*

Arranque ramos apoiam a preparação de um novo lançamento de produção. Permitem para pontos de última hora de i’s e t’s de cruzamento. Além disso, permitem correcções de bugs forminor e preparação de meta-dados para um lançamento (número de versão, builddates, etc.). Ao fazer todo este trabalho sobre um ramo de lançamento, o developbranch é liberado para receber características para o próximo grande lançamento.

O momento chave para ramificar um novo ramo de lançamento de develop é quando o desenvolvimento (quase) reflecte o estado desejado do novo lançamento. Pelo menos todas as funcionalidades que são alvo do lançamento a ser construído devem ser fundidas emdevelop neste momento. Todas as funcionalidades destinadas a lançamentos futuros não podem – devem esperar até que o ramo de lançamento seja ramificado.

É exactamente no início de um ramo de lançamento que o próximo lançamento recebe um número de versão – e não um número anterior. Até esse momento, o developbranch reflectiu as alterações para a “próxima versão”, mas não é claro se essa “próxima versão” acabará por se tornar 0.3 ou 1.0, até que o ramo de lançamento seja iniciado. Essa decisão é tomada no início do ramo de lançamento e é levada em conta pelas regras do projecto sobre o número de versão bumping.

Criar um ramo de lançamento ¶

Lançamento de ramos são criados a partir do develop ramo. Por exemplo, sayversion 1.1.5 é o lançamento de produção actual e temos um grande lançamento a chegar. O estado de develop está pronto para a “próxima versão” e decidimos que esta se tornará a versão 1.2 (em vez de 1.1.6 ou 2.0). Por isso, retiramos o ramo de lançamento e damos um nome que reflecte o novo número da versão:

$ git checkout -b release-1.2 developSwitched to a new branch "release-1.2"$ ./bump-version.sh 1.2Files modified successfully, version bumped to 1.2.$ git commit -a -m "Bumped version number to 1.2" Bumped version number to 1.21 files changed, 1 insertions(+), 1 deletions(-)

Depois de criarmos um novo ramo e mudarmos para ele, batemos com o número da versão. Aqui, bump-version.sh é um script de shell fictício que muda alguns ficheiros na cópia de trabalho para reflectir a nova versão. (Isto pode, evidentemente, ser uma mudança manual – o que significa que alguns ficheiros mudam). Depois, o número da versão alterada é atribuído.

Este novo ramo pode existir ali por algum tempo, até que o lançamento possa ser lançado definitivamente. Durante esse tempo, correcções de bugs podem ser aplicadas neste ramo(em vez de no ramo develop). É estritamente proibido acrescentar aqui grandes novas características. Devem ser fundidas em develop, e portanto, esperar pelo próximo grande lançamento.

Finalizar um ramo de lançamento ¶

Quando o estado do ramo de lançamento estiver pronto para se tornar um lançamento real, algumas acções têm de ser levadas a cabo. Primeiro, o ramo de lançamento é fundido emmaster (uma vez que cada compromisso em master é uma nova versão por definição,lembre-se). Em seguida, os commit on master devem ser marcados para facilitar a referência futura a esta versão histórica. Finalmente, as alterações feitas no releasebranch precisam de ser fundidas novamente em develop, para que futuros lançamentos também contenham estas correcções de bugs.

Os dois primeiros passos em Git:

$ git checkout masterSwitched to branch 'master'$ git merge --no-ff release-1.2Merge made by recursive.(Summary of changes)$ git tag -a 1.2

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

Edit: Mais vale usar o -s ou -u <key> bandeiras para sinalizar criptograficamente a sua tag.

Para manter as alterações feitas no ramo de lançamento, precisamos de fundir essas de volta em develop, no entanto. Em Git:

$ git checkout developSwitched to branch 'develop'$ git merge --no-ff release-1.2Merge made by recursive.(Summary of changes)

Este passo pode muito bem levar a um conflito de fusão (provavelmente até mesmo, uma vez que temos o número da versão). Se assim for, conserte-o e comprometa-se.

Agora estamos realmente acabados e o ramo de lançamento pode ser removido, uma vez que já não precisamos mais dele:

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

Ramos hotfix ¶

Pode ramificar-se de:masterDeve fundir-se de novo:developemasterConvenção de nomenclatura de sucursais:hotfix-*

Os ramos hotfix são muito parecidos com os ramos de libertação na medida em que também se destinam a preparar-se para uma nova libertação de produção, embora não planeada. Surgem da necessidade de agir imediatamente sobre um estado indesejável de uma reversão de produção ao vivo. Quando um bug crítico numa versão de produção deve ser resolvido de imediato, um ramo hotfix pode ser ramificado a partir da etiqueta correspondente no ramo principal que marca a versão de produção.

A essência é que o trabalho dos membros da equipa (no ramo develop) pode continuar, enquanto outra pessoa está a preparar uma rápida correcção de produção.

Criar o ramo hotfix ¶

Os ramos hotfix são criados a partir do ramo master. Por exemplo, digamos que a versão1.2 é a actual versão de produção a funcionar ao vivo e a causar problemas devido a um grave bug. Mas as alterações em develop são ainda instáveis. Podemos então ramificar de um ramo hotfix e começar a corrigir o problema:

$ git checkout -b hotfix-1.2.1 masterSwitched to a new branch "hotfix-1.2.1"$ ./bump-version.sh 1.2.1Files modified successfully, version bumped to 1.2.1.$ git commit -a -m "Bumped version number to 1.2.1" Bumped version number to 1.2.11 files changed, 1 insertions(+), 1 deletions(-)

Não se esqueça de bater o número da versão após a ramificação!

Então, corrija o bug e submeta a correcção em um ou mais commits separados.

$ git commit -m "Fixed severe production problem" Fixed severe production problem5 files changed, 32 insertions(+), 17 deletions(-)

Finalizando um ramo hotfix ¶

Quando terminado, o bugfix precisa de ser fundido novamente em master, mas também precisa de ser fundido novamente em develop, de modo a salvaguardar que a correcção de erros também seja incluída no próximo lançamento. Isto é completamente semelhante à forma como as releasebranches são terminadas.

First, update master e etiquetar a release.

$ git checkout masterSwitched to branch 'master'$ git merge --no-ff hotfix-1.2.1Merge made by recursive.(Summary of changes)$ git tag -a 1.2.1

p>Edit: Mais vale usar o -s ou -u <key> bandeiras para sinalizar criptograficamente a sua tag.

Next, incluir a correcção de erros em develop, também:

$ git checkout developSwitched to branch 'develop'$ git merge --no-ff hotfix-1.2.1Merge made by recursive.(Summary of changes)

A única excepção à regra aqui é que, quando um ramo de lançamento existe actualmente, o hotfix tem de ser fundido nesse ramo de lançamento, em vez de develop. A retrofusão da correcção de bugs no ramo de lançamento acabará por resultar na fusão da correcção de bugs em develop também, quando o ramo de lançamento estiver terminado. (Se o trabalho em develop necessitar imediatamente desta correcção de bugs e não pode esperar que o ramo de lançamento esteja terminado, pode fundir em segurança a correcção de bugs em develop agora também já.)

Finalmente, remover o ramo temporário:

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

Sumário ¶

p>Embora não haja nada de realmente chocante neste modelo de ramificação, a figura “bigpicture” com que este post começou revelou-se tremendamente útil nos nossos projectos. Forma um modelo mental elegante que é fácil de compreender e permite aos membros da equipa desenvolver um entendimento partilhado dos processos de ramificação e de lançamento.

É fornecida aqui uma versão PDF de alta qualidade da figura. Vá em frente e pendure-o na parede para referência rápida em qualquer altura.

Update: E para quem o tenha solicitado: aqui está o modelogitflow-model.src.key da imagem do diagrama principal (Apple Keynote).


Git-branching-model.pdf

Outros posts neste blogue

  • Ferramentas eléctricas para uso diário
  • Uma introdução aos descodificadores
  • Dívida técnica é dívida real
  • Bela código
  • Bela mapa

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *