Team Foundation Server

Diretrizes de ramificação e mesclagem do Visual Studio TFS

Bill Heys

Baixar o código de exemplo

Desde seu início em 2006, a equipe Visual Studio ALM Rangers tem trabalhado na divisão de desenvolvedores da Microsoft para promover a colaboração entre os grupos de produtos do Visual Studio, o Microsoft Services e a comunidade MVP (Microsoft Most Valuable Professional). A declaração de visão padrão da equipe Rangers é “acelerar a adoção do Visual Studio com soluções fora da banda para recursos ou diretrizes que estão faltando” lidando com a funcionalidade ausente e eliminando os fatores que impedem a adoção. A colaboração entre a variedade de especialistas em tecnologia e negócios permite que os Rangers capacitem as comunidades compartilhando experiências do mundo real. (Conheça mais sobre os Rangers em msdn.microsoft.com/vstudio/ee358786.)

O Guia de Ramificação 2010 do Visual Studio Team Foundation Server (TFS) (tfsbranchingguideiii.codeplex.com) consolida a orientação prática e perspicaz sobre ramificação e mesclagem com o Visual Studio TFS 2010 ao oferecer laboratórios práticos e ensinamentos aprendidas com a comunidade. Neste artigo, apresentaremos alguns dos cenários de ramificação avançada nos quais vamos trabalhar para a próxima versão da diretriz.

Ramificação: ‘o estado de hoje da nação’

As Diretrizes de ramificação Rangers tiveram início como um projeto da equipe Rangers após o lançamento do Visual Studio 2005 e do TFS 2005. Esta primeira versão das diretrizes Rangers foi publicada no CodePlex em 2007.

Em 2008, os Rangers deram início ao projeto de Diretrizes de ramificação II. Nesta segunda versão, reorganizamos as diretrizes em um conjunto de documentos relacionados (Principal, Cenários, Perguntas e respostas, Diagramas, Pôsteres e assim por diante). Cada um dos documentos secundários foi desenvolvido com base nas diretrizes primárias, como apresentado no documento de ramificação Principal. As Diretrizes de ramificação Rangers II foram publicadas no CodePlex no final de 2008.

Em 2009, a equipe Rangers deu início a um novo projeto de Diretrizes de ramificação: Diretrizes de ramificação 2010. Esta terceira versão se concentrou em mostrar muitos dos novos recursos de ramificação disponíveis no Visual Studio 2010 e no TFS 2010. Um importante recurso novo da versão 2010 é a visualização de ramificação.

Em parte porque a versão mais recente foi intitulada Guia de Ramificação 2010 do Visual Studio TFS Rangers, houve uma certa confusão aparente sobre se essas diretrizes se aplicam exclusivamente ao Visual Studio 2010. Queremos esclarecer que as práticas recomendadas e as orientações apresentadas nos documentos de diretrizes de 2010 podem continuar sendo aplicadas a versões anteriores do Visual Studio e do TFS. Na verdade, a equipe Rangers tem recebido comentários positivos de pessoas que usam outras ferramentas para gerenciamento de controle de origem (SCM, Source Control Management).

Para 2011, a equipe Rangers está novamente planejando uma atualização das Diretrizes de ramificação Rangers.

Sinta-se à vontade para postar dúvidas, comentários imparciais ou outras questões no site do CodePlex.

Objetivos e estratégias de ramificação

Um objetivo importante da ramificação é proporcionar isolamento entre fluxos de trabalho paralelos. Nas atuais Diretrizes 2010 de ramificação Rangers, tendemos a nos concentrar mais no isolamento da versão do que no isolamento durante uma iniciativa de desenvolvimento complexo.

Em muitos casos, todas as atividades de desenvolvimento para a próxima versão de um produto podem ser conduzidas por uma única equipe de desenvolvimento. Neste caso simples, é necessária apenas uma ramificação de desenvolvimento para isolar o desenvolvimento da estabilização contínua (Ramificação principal) ou de Sustained Engineering (comercialização de lançamentos de produção, junto com suporte contínuo para hotfix e service pack).

Muitas vezes os Rangers são indagados a respeito de suporte para iniciativas de desenvolvimento mais complexas, em que uma ramificação de desenvolvimento não oferece flexibilidade ou isolamento suficiente para um esforço maior de desenvolvimento de produto. Na próxima versão das Diretrizes de ramificação Rangers, incluiremos mais orientações sobre como trabalhar com cenários de desenvolvimento complexos, como o desenvolvimento da equipe de recursos.

Gostamos de separar as discussões sobre estratégias de ramificação em duas áreas:

  1. Como a minha organização desenvolve software? Temos uma estrutura de equipe menor e mais simples ou precisamos comportar equipes mais complexas com esforços de desenvolvimento em paralelo?
  2. Como a minha organização libera software para seus clientes, sejam internos ou externos? Precisamos dar suporte a várias versões liberadas? Precisamos oferecer hotfixes ou service packs?

Em alguns cenários, a estratégia de liberação de uma organização pode influenciar o processo de desenvolvimento, principalmente a estrutura das equipes de desenvolvimento. No entanto, com frequência a complexidade do processo de liberação e da estratégia de ramificação pode ser independente da complexidade do processo de desenvolvimento e da estratégia de ramificação.

Na elaboração de uma estratégia de ramificação, considere não apenas a estrutura de ramificação, mas também o processo de ramificação. Por exemplo, no plano de ramificação básica descrito nas Diretrizes 2010 de ramificação Rangers, existem somente três ramificações (Principal, Desenvolvimento e Liberação). Uma boa estratégia de ramificação descreverá as relações de ramificação (por exemplo, Principal é pai das ramificações Desenvolvimento e Liberação).

Além disso, uma estratégia de ramificação deve descrever o processo subentendido pela estrutura de ramificação. Por exemplo, com que frequência você compila código na ramificação Principal? Com que frequência você mescla código (pró-integração) da ramificação Principal com as ramificações de Desenvolvimento? Quais são as condições para mesclar código (integração reversa) de uma ramificação de Desenvolvimento de volta para a ramificação Principal e assim por diante? Vamos discutir alguns cenários de ramificação típicos.

O cenário da equipe de recursos

Com frequência as organizações precisam de uma estratégia de ramificação para acomodar esforços de desenvolvimento grandes e complexos envolvendo diversas equipes de desenvolvimento ou equipes de recursos trabalhando em paralelo. Surgem dúvidas sobre o número necessário de ramificações de desenvolvimento separadas. Se tenho várias ramificações de desenvolvimento, quando e como integro recursos desenvolvidos por uma equipe com recursos desenvolvidos por outras equipes? Respostas para essas perguntas devem ser incorporadas a uma estratégia de desenvolvimento/ramificação.

Vamos começar descrevendo uma iniciativa de desenvolvimento complexa. Embora possa haver um cronograma de liberações comum para a iniciativa inteira, podem existir várias equipes de recursos separadas trabalhando em etapas independentes. À medida que forem concluídos e testados, esses recursos serão integrados à ramificação Principal.

Em uma única equipe, desenvolvedores individuais usam espaços de trabalho locais para isolar as alterações feitas por eles daquelas feitas por outros integrantes da equipe. Ramificações Equipe de recursos são uma boa maneira de isolar as alterações feitas por uma Equipe de recursos daquelas feitas por outras Equipes de recursos trabalhando em paralelo no mesmo produto. Sem o isolamento da Equipe de recursos, as alterações feitas por uma equipe podem introduzir mudanças significativas que afetam a velocidade das outras equipes.

A criação da estrutura de ramificação para o isolamento de Equipe de recursos é relativamente simples. Mas, primeiro, é necessário planejar como as ramificações Equipe de recursos serão integradas posteriormente. Adicionamos uma “ramificação de integração” entre a ramificação Principal e as ramificações Equipe de recursos, como ilustrado na Figura 1?

Figura 1 Ramificações Principal e Integração

Ou eliminamos a camada de integração e integramos as alterações da Equipe de recursos de outro modo? Qual é a prática recomendada?

Recomendamos minimizar o número de níveis em uma hierarquia de ramificação. Adicionar uma camada de integração entre as ramificações Principal e Equipe de recursos efetivamente duplica as mesclagens necessárias para mover alterações entre a ramificação Principal e as ramificações Recursos. A ramificação ajuda a isolar alterações, mas seu custo é o consequente esforço necessário para mesclar código entre ramificações e resolver conflitos de mesclagem que sempre se fazem presentes. Adicionar uma camada de integração duplica as mesclagens e, provavelmente, o esforço necessário para resolver conflitos de mesclagem.

Se eliminamos a camada de integração, diminuímos o número de camadas na nossa hierarquia de ramificações. Mas onde acontece a integração da Equipe de recursos 1 com a Equipe de recursos 2 e onde ela é testada? Para manter a ramificação Principal o mais estável possível, evite introduzir nela alterações de integração não testada. Sem uma camada de integração, a mesclagem de recursos e o teste da integração devem ser feitos de maneira controlada nas próprias ramificações Equipe de recursos.

Recomendamos fazer compilações diárias na ramificação Principal (estável) e, após uma boa compilação diária, fazer uma mesclagem da ramificação Principal para a ramificação Desenvolvimento. Não mescle código de uma ramificação Recursos de volta para Principal até que o código da ramificação Recursos esteja relativamente estável. Em outras palavras, a ramificação Recursos deve ser aprovada no controle de qualidade para poder ser mesclada com Principal.

Somente quando o código em uma ramificação Recursos é considerado “pronto para liberação” ou “pronto para compartilhamento com outras equipes” é que devemos cogitar a integração dessa ramificação Recursos com Principal ou com outras ramificações Recursos. A Figura 2 ilustra esse processo após cada etapa “pronto para liberação”.

Figura 2 Ramificação Recursos

Em seguida, estão as etapas do processo:

  • Antes de mesclar a ramificação Equipe de recursos 1 com Principal, faça uma última mesclagem (pró-integração ou FI, do termo em inglês Forward Integration) de Principal com a ramificação Equipe de recursos 1.
  • Execute um teste final dessa integração de código de Principal com o código da ramificação Equipe de recursos 1.
  • Uma vez que o código na ramificação Equipe de recursos 1 esteja estável, mescle-o (integração reversa ou RI, do termo em inglês Reverse Integration) de volta a Principal.
  • Nessa etapa, o código de Principal incorpora o código de Equipe de recursos 1.
  • Faça uma compilação e teste em Principal, equivalente à compilação diária. Na próxima compilação bem-sucedida de Principal, mescle Principal com cada uma das ramificações Equipe de recursos. Inicialmente, isso fará com que o código de Equipe de recursos 1 seja mesclado com o código de Equipe de recursos 2.
  • Teste a integração do código de Equipe de recursos 1 com o código de Equipe de recursos 2 na ramificação Equipe de recursos 2.
  • Quando o código de Equipe de recursos 2 estiver pronto para liberação ou para ser compartilhado com outras equipes, mescle o código de Equipe de recursos 2 novamente com Principal. Mas primeiro faça uma última mesclagem de Principal com Equipe de recursos 2 e teste a integração final.

Observação: um requisito importante para omitir uma camada de integração separada é a capacidade de usar testes automatizados para as integrações. Os testes automatizados ajudam a reduzir o impacto sobre a velocidade do código (ou seja, a produtividade da equipe de recursos) conforme a equipe trabalha para identificar e resolver bugs decorrentes da mesclagem de muitas alterações em uma ramificação.

Se não houver testes automatizados disponíveis para verificar as alterações de integração, corre-se o risco de a velocidade de código das equipes de recursos ser afetada negativamente à medida que realizam testes manuais para identificar e resolver bugs. Neste cenário, uma organização pode considerar a possibilidade de adicionar uma camada de integração entre as ramificações Principal e Recursos. Como observado anteriormente, a camada de integração pode resultar em um aumento da mesclagem e da resolução de conflitos de mesclagem. Mas a vantagem é que essa camada pode possibilitar a integração com menor impacto sobre a velocidade de código das equipes de recursos.

Uma boa estratégia de ramificação exige uma estrutura de ramificação segura aliada a um processo de ramificação seguro para garantir a velocidade máxima de código às Equipes de recursos e, ao mesmo tempo, manter a estabilidade da ramificação Principal.

Cenário de compartilhamento de código comum

Compartilhar código comum entre projetos é um desafio para muitas organizações. Existem três técnicas principais que podem ser usadas para compartilhar código entre projetos ou soluções no Visual Studio:

  • Vinculação de arquivos
  • Compartilhamento binário (assembly)
  • Compartilhamento de código-fonte

Como discutimos em outro ponto deste artigo, também existem várias técnicas de isolamento de código:

  • Isolamento de projetos por equipe
  • Isolamento de ramificação
  • Isolamento de espaço de trabalho

A escolha da estratégia de compartilhamento de código correta para a sua organização tende a envolver uma combinação de técnicas de compartilhamento de código e técnicas de isolamento.

Vinculação de arquivos: trata-se de um recurso do Visual Studio (Adicionar Item Existente) pelo qual vários projetos podem compartilhar uma referência a um único arquivo de origem. A vinculação de arquivos é mais apropriada para projetos pequenos com um número limitado de arquivos compartilhados. (É semelhante ao compartilhamento de arquivos no Visual Source Safe.)

Com a vinculação de arquivos, há somente uma versão do arquivo de origem vinculado a ser mantida. As alterações feitas no arquivo vinculado são recebidas imediatamente por todos os projetos vinculados ao arquivo. A desvantagem dessa técnica é que as alterações feitas no arquivo vinculado devem ser coordenadas com todas as equipes de projeto dependentes. Mesmo alterações cuidadosamente coordenadas podem gerar alterações significativas nos projetos dependentes.

Compartilhamento binário (referências a assembly): com o compartilhamento binário, uma solução do Visual Studio faz referência ao código compartilhado comum usando referências a assembly. Aqui, a compilação da solução dependente não compila também o código-fonte compartilhado comum. A compilação de projetos dependentes será mais rápida se forem usadas referências a assembly em vez de referências de projeto.

As equipes que possuem o código comum têm propriedade e controle totais, o que teoricamente significa que o controle, o controle de versão e a qualidade do produto tendem a ser melhores, e as complexidades de ramificação e mesclagem são evitadas.

Como as equipes que reutilizam o código comum não têm acesso ao código-fonte comum, elas dependem da equipe proprietária para adicionar novos recursos e resolver bugs no código compartilhado comum.

Os assemblies do código comum podem ser compartilhados via cópia para um compartilhamento de arquivos conhecido que possa ser referenciado por projetos dependentes. Pode ser necessário adicionar os assemblies ao cache global de assemblies. Como alternativa, os assemblies podem ser copiados do código comum Projeto de Equipe para uma pasta bin sob a ramificação Principal do projeto dependente. 

Compartilhamento de código-fonte: com o compartilhamento de código-fonte no Visual Studio, um projeto dependente usa uma referência de projeto para o código compartilhado comum. Quando a solução é compilada, todos os projetos também são, inclusive os projetos de código compartilhado comum. No caso de projetos complexos, ter muitas referências de projeto a código comum pode aumentar o tempo de compilação consideravelmente.

Neste cenário, o código compartilhado comum pertence e é gerenciado por um equipe em seu próprio Projeto de Equipe do TFS. Para compartilhar esse código comum, primeiro ramifique o código para uma pasta com o Projeto de Equipe do projeto consumidor (dependente), da seguinte forma:

  • Crie uma pasta dentro do Projeto de Equipe do projeto dependente chamada “Share” (por exemplo, $\Product1\Share).
  • Ramifique a ramificação Principal da Biblioteca Comum (por exemplo, EnterpriseLibrary) para a pasta Share do Projeto dependente (por exemplo, ramificação $\Enterprise Library\Principal para $\Product1\Share\EnterpriseLibrary).
  • Adicione os projetos de código comum apropriados à solução do projeto dependente.
  • Crie referências de projeto do projeto dependente para os projetos de código comum existentes na solução.

Observação: não há suporte para ramificações aninhadas no TFS 2010. Pode ocorrer um erro de ramificação aninhada quando você tenta executar uma operação de ramificação que faria com que uma nova ramificação fosse criada (acima ou abaixo de uma ramificação existente) na estrutura de pastas (veja aFigura 3*).*

Figura 3 Exemplo de ramificação aninhada causando erro no Team Foundation Server 2010

Sua organização precisa decidir se as alterações feitas no código-fonte compartilhado comum devem ou não ser permitidas dentro de cada projeto dependente. Para evitar mudanças, depois de ramificar da Biblioteca Comum, é possível tornar a nova ramificação somente leitura. Todas as alterações feitas no código-fonte comum devem então ser feitas no Projeto de Equipe da Biblioteca Comum e mescladas nos Projetos de Equipe dos projetos dependentes.

Como alternativa, podem ser feitas alterações no código-fonte compartilhado dentro de um Projeto de Equipe dependente. Essas alterações podem ser mescladas (por meio da integração reversa) novamente no Projeto de Equipe da Biblioteca Comum. Sua organização precisa gerenciar essas alterações com cuidado para evitar incompatibilidades que tornem difícil ou impossível a mesclagem dessas alterações novamente com a Biblioteca Comum, o que pode resultar em várias cópias do código compartilhado.

Cenário de modelagem e ferramentas de arquitetura

No Visual Studio Ultimate, você pode criar modelos UML e de Camada que existem em projetos próprios do Visual Studio e podem conter muitos pacotes, relacionados a diferentes partes da solução (consulte Diretrizes de Ferramentas de Arquitetura, em vsarchitectureguide.codeplex.com, e O aplicativo de modelagem, em msdn.microsoft.com/library/57b85fsc.aspx).

Para investigar se a ramificação e a mesclagem são possíveis com modelos, podemos criar um ambiente de teste simples com três cenários, como ilustrado na Figura 4.

Figura 4 Cenários de avaliação em um ambiente de teste para analisar a possibilidade de ramificação e mesclagem

Podemos criar uma ramificação Principal, com uma solução contendo um projeto modelo com um diagrama de classes UML vazio como projeto estável hipotético. Em seguida, podemos ramificar Principal para Scenario1, Scenario2 e Scenario3 e, depois, ramificar cada cenário para uma ramificação Dev1 e Dev2 representando equipes de desenvolvimento, como ilustrado na Figura 5.

Figura 5 Demonstração de ramificação Projeto de Equipe, como exibida no Source Explorer

É evidente que não temos problemas com ramificação, mas podemos fazer a integração reversa (mesclagem) de alterações no modelo?

Em Scenario1, as equipes não fazem alterações no modelo e, em Scenerio2, apenas uma das duas equipes expande os modelos. A RI resultante das ramificações Desenvolvimento para a ramificação de cenário associado é rotineira, com o modelo Scenario1 inalterado e o modelo Scenario2 atualizado.

Scenario3 é um exemplo mais realista, em que as duas equipes atualizam o modelo. A Equipe Dev1 cria duas classes e a Equipe Dev2 cria uma classe.

O leitor assíduo perceberá que as duas equipes criaram uma classe chamada Class1, com operações diferentes.

A integração reversa da primeira das duas ramificações de desenvolvimento com a ramificação Scenario3 dá a falsa sensação de segurança de que a mesclagem será fácil. Porém, quando a segunda equipe mescla alterações feitas na ramificação Scenario3, conflitos em três arquivos (.classdiagram, .layout e .uml) impedem o check-in, como ilustrado na Figura 6.

Figura 6 Uma mesclagem causa conflitos devido às alterações feitas por duas equipes da classe Class1

Poderíamos selecionar as opções “Manter Versão de Ramificação do Destino” ou “Escolher Versão de Ramificação da Fonte” e responder a pergunta sobre se a mesclagem é possível com um “sim”. No entanto, o resultado seria uma equipe muito triste perdendo suas alterações. A alternativa é selecionar a opção “Mesclar Alterações na Ferramenta de Mesclagem” para uma mesclagem manual, que não é prática nem intuitiva e, para a maioria de nós, pode levar a erros.

A ramificação e mesclagem de modelos de arquitetura é, portanto, possível, mas é recomendável? O problema com o modelo UML é que as visualizações (por exemplo, o diagrama de classes) estão distribuídas entre três arquivos principais (.layout, .classdiagram e .uml), como ilustrado na Figura 7.

Figura 7 Visualizações distribuídas entre três arquivos principais

O arquivo .layout define o tamanho e as posições das formas no modelo. O arquivo .uml é o modelo “mestre”, e o arquivo .classdiagram mantém um cache do conteúdo de .uml, que está presente no diagrama.

As mesclagens também são difíceis, uma vez que as edições normais nas ferramentas de modelagem são validadas e, com frequência, acrescentadas pela ferramenta para evitar estados inválidos. Essa validação não acontece em uma mesclagem XML pura, o que traz o risco de se criar modelos inválidos que podem nem mesmo abrir.

Se cada equipe fizesse alterações somente em seus próprios diagramas e se essas alterações representassem classes em pacotes diferentes, o problema poderia ser reduzido porque a maioria das alterações apareceria em arquivos separados. Ainda assim, inevitavelmente haverá alguns casos em que as relações que ultrapassam os limites dos pacotes serão alteradas.

Na realidade, algumas equipes desejarão ramificar quando criarem novas iterações de produto, o que leva à bifurcação de código-fonte, documentação e modelos. Modelos como o diagrama de atividade, sequência, camada e classe são bons exemplos de modelos que evoluem com as iterações, enquanto a equipe de entrega dá continuidade ao desenvolvimento e à manutenção básicos. Portanto, os modelos podem e, muitas vezes, de fato se expandem em duas ou mais ramificações, o que significa que, em algum momento, encontraremos o cenário de ramificação e o cenário de mesclagem, sempre desafiador.

Todos os modelos atuais são bons candidatos para ramificação, mas nenhum contribui para mesclagem. Com a perspectiva de uma mesclagem desafiadora e propensa a erros, a recomendação é dupla:

  • evite a mesclagem definindo uma exibição de modelo e solução que represente classes em pacotes separados. As diretrizes de ferramentas de arquitetura propõem uma exibição de solução, mostrada na Figura 8, e uma exibição de modelo, ilustrada na Figura 9, com base em pacotes. Deve-se tomar certo cuidado quando os diagramas têm conteúdo de vários pacotes, o que é possível para diagramas de Classe, Componente e Caso de Uso. Nesse caso, para evitar quaisquer conflitos, os usuários devem evitar a edição dos metadados de elementos que pertencem ao pacote “externo”.

    Figura 8 A estrutura proposta baseada em pacotes como vista na exibição de solução

    Figura 9 A estrutura proposta baseada em pacotes como vista no Gerenciador de Modelos UML

  • Mantenha os modelos em uma ramificação que não será bifurcada, semelhante a componentes compartilhados.

A desvantagem é editar os modelos visual e manualmente em uma ramificação usando as opções “Manter Versão de Ramificação do Destino" ou "Escolher Versão de Ramificação da Fonte”, como ilustrado na Figura 10.

Figura 10 Uma mesclagem manual de edição de modelo

Por exemplo, os modelos divergem em duas ramificações, como mostrado, e são mesclados manualmente (etapa 3) via comparação visual dos modelos e atualização manual do modelo na ramificação superior. A ramificação com o modelo consolidado é então submetida à integração reversa para Principal (etapa 4), e a outra ramificação com o modelo desatualizado é submetida à integração reversa (etapa 5) usando “Manter Versão de Ramificação de Destino” na resolução de conflitos de modelo.

Em suma, ainda não existe uma boa história sobre mesclagem automática de modelos. A estratégia recomendada é evitar um cenário de ramificação e mesclagem com os modelos ou usar a edição visual e manual de modelos antes de mesclar.

Agora introduzimos uma série de novos cenários de ramificação, que você poderá encontrar em um complexo ambiente real. No próximo artigo desta série, examinaremos projetos de equipe e coleções de projetos de equipe.

Bill Heys é um consultor sênior da Microsoft Americas Consulting Services na Nova Inglaterra. Como Visual Studio ALM Ranger, Heys é especialista em desenvolvimento de aplicativos personalizados e gerenciamento do ciclo de vida de aplicativos usando o Visual Studio. Seu blog está em blogs.msdn.com/b/billheys.

Willy-Peter Schaub é arquiteto de soluções da equipe Visual Studio ALM Rangers do Microsoft Canada Development Center. Desde meados da década de 80, tem se empenhado em buscar a simplicidade e a facilidade de manutenção na engenharia de software. Seu blog está em blogs.msdn.com/b/willy-peter_schaub.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Marcel de Vries, Jens Jacobsen, Bijan Javidi e Alan Wills