Muda é um sistema de controle de código-fonte escalonável, fácil de usar e de código aberto que alimenta o monorepo do Meta. Conforme discutido na conferência GitMerge 2024 sessão sobre ramificaçãoprojetar e implementar fluxos de trabalho ramificados para grandes monorepos é um problema desafiador com diversas compensações entre escalabilidade e experiência do desenvolvedor.
Após a conferência, projetamos, implementamos e abrimos o código-fonte de nossa solução de ramificação monorepo no Sapling. Embora o código já seja de código aberto, neste artigo compartilhamos aprendizados sobre:
- Como resolvemos as compensações de escalabilidade e experiência do desenvolvedor no design e na implementação.
- Que problemas isso resolveu.
- Que feedback recebemos de outros desenvolvedores da Meta.
O principal insight técnico é que dois fluxos de trabalho – ramificação de repositório completo não mesclável e ramificação de diretório mesclável — resolveu todos os problemas relacionados à ramificação de um conjunto grande e diversificado de produtos construídos na Meta.
Esperamos que o código-fonte aberto do Sapling e os aprendizados compartilhados neste artigo beneficiem a indústria em geral e as comunidades de código aberto.
Como o controle de origem é tratado no Meta
Na Meta, nossas equipes de engenharia trabalham em um grande monorepo com uma única filial principal. Essa abordagem permite o gerenciamento unificado de dependências, refatoração em larga escala, colaboração mais fácil e reutilização de código entre projetos. No entanto, esta abordagem introduz desafios para as equipas que têm de gerir múltiplas versões do seu código.
Em configurações de vários repositórios, as equipes podem contar com ramificações do repositório para gerenciar diferentes versões. O controle de origem fornece ferramentas, como seleção seletiva e mesclagem, que permitem gerenciar as diferenças entre as versões.
No monorepo, entretanto, as ramificações do repositório não funcionam tão bem para isso. As ramificações afetam todo o repositório, portanto, criar uma ramificação significa que projetos e dependências não relacionados permanecerão congelados e rapidamente se tornarão obsoletos.
Neste artigo nos referimos à ramificação completa do repositório como ramificação de repositório completo. O que aprendemos é que para fluxos de trabalho que não exigem fusão de volta ao branch principal (por exemplo, lançamentos de produtos em que o branch deixa de existir após a conclusão do lançamento e o desenvolvimento volta para o branch principal), a ramificação de repositório completo é uma boa solução. No Sapling, este fluxo de trabalho é bem suportado com o marcador de sl família de comandos.
No entanto, para fluxos de trabalho de desenvolvimento de produtos em que é necessária a fusão com a ramificação principal, aprendemos que a ramificação de repositório completo não é uma abordagem escalável. Isso ocorre porque as mesclagens de repositório completo criam commits de mesclagem com vários pais, tornando o gráfico de commit largo (alto fator de ramificação) e não linear. Em grandes monorepos, isso cria problemas de desempenho para operações como registro sl e culpa. Manter um gráfico de commit linear, onde a maioria dos commits tem um único pai, é crucial para manter essas operações rápidas para todos os usuários do monorepo, não apenas para aqueles que utilizam ramificações.
A principal limitação é que as ramificações full-repo são tudo ou nada. Se você precisar corrigir uma versão legada ou manter uma variante personalizada para um projeto específico, não poderá criar uma ramificação para a parte que possui. A ramificação bifurca tudo.
Um padrão comum ao tentar resolver esse problema era as equipes fazerem várias cópias de seu código. No entanto, ao fazer isso, eles perdem muitas das ferramentas padrão do desenvolvedor para gerenciar suas filiais. Isso resultou em esforço duplicado e cópia de patches entre diretórios sujeita a erros.
Ramificação de diretório: solução de ramificação Monorepo da Sapling
Para resolver esses desafios, introduzimos um novo conjunto de ferramentas de controle de origem no Sapling que pode ser usado para implementar um novo tipo de ramificação: ramificação de diretório. Isso preenche a lacuna entre o uso de múltiplas ramificações de repositório e a manutenção de cópias de código como diretórios separados.
Com essas ferramentas, você pode tratar os diretórios no monorepo da mesma forma que as ramificações tradicionais do repositório. Você cria ramificações copiando o código, mantém o código selecionando e mesclando alterações entre diretórios como se fossem ramificações, e observa o histórico de cada diretório no contexto das cópias e mesclagens que foram feitas.
Crucialmente, embora as ramificações de diretório suportem a fusão entre diretórios, no nível do gráfico de commit do monorepo, elas aparecem como commits lineares. Isso resolve o desafio de escalabilidade com as confirmações de mesclagem no nível do repositório e ainda fornece fluxos de trabalho de mesclagem no nível do diretório.
Como a ramificação de diretório é implementada no Sapling
A ramificação de diretório no Sapling é implementada usando uma série de operações centradas no subárvore sl comando.
Para ramificar um diretório, você usa o cópia da subárvore sl comando para copiar um diretório (ou arquivo), na versão atual ou de qualquer versão histórica, para um novo local no repositório. O Sapling registra metadados no commit que rastreia o diretório de origem, a revisão de origem e o relacionamento de cópia, o que nos permite recuperar o histórico completo de todos os arquivos no novo branch. Se o código que você deseja ramificar ainda não estiver no monorepo, você pode usar importação de subárvore sl para criar uma ramificação de diretório de uma ramificação de repositório externo.
Depois de ter uma ramificação de diretório, você pode usar enxerto de subárvore sl e mesclagem de subárvore sl para selecionar ou mesclar alterações entre ramificações de diretório. Essas operações usam os metadados de cópia/mesclagem armazenados para reconstruir o relacionamento entre diretórios, permitindo que o Sapling execute mesclagens de três vias entre ramificações de diretórios. O algoritmo de mesclagem encontra o ancestral comum das duas ramificações de diretório (usando os metadados de cópia) e executa uma mesclagem de três vias padrão, assim como faria para mesclagens regulares de repositórios, mas com escopo para o conteúdo específico do diretório.
O sistema de construção e a integração mais ampla das ferramentas do desenvolvedor
Uma vantagem dessa abordagem é que as versões mais recentes de todas as ramificações do diretório ficam visíveis ao mesmo tempo. Isso significa que a integração contínua (CI) pode ser testada em diversas ramificações com um único checkout, e você pode ter certeza de que não há ramificações antigas ocultas que inesperadamente ainda estejam em uso.
Na Meta usamos Buck2 como nosso sistema de construção. Quando um componente depende de outro componente que usa ramificação de diretório, usamos Buck modificadores de configuração (ou seja, construção de fanfarrão com o -m flag) para nos permitir selecionar qual branch está sendo usado.
Uma desvantagem da ramificação de diretório é que as pesquisas de código podem resultar em vários resultados para cada uma das ramificações. É relevante que o código procurado apareça em vários locais, porém pode ser difícil consultar os resultados de vários ramos se eles estiverem misturados. Sistemas de busca de código capazes de classificar os resultados podem resolver esse problema.
Feedback do usuário sobre ramificação de diretório
A introdução da ramificação de diretórios foi um sucesso, com um grande e diversificado conjunto de equipes de engenharia dentro do Meta adotando-a para gerenciar múltiplas versões de código. Algumas equipes também acharam útil congelar temporariamente a maior parte do monorepo para estabilidade do desenvolvimento, permanecendo em um commit antigo e usando ramificação de diretório para mesclar alterações em projetos específicos, combinando efetivamente fluxos de trabalho de ramificação de repositório completo e ramificação de diretório.
Observamos o seguinte três temas comuns de razões válidas para adotar a ramificação de diretório:
1.) Quando a IC é proibitivamente cara ou as alterações podem causar grandes interrupções. Algumas equipes da Meta usaram ramificações de diretório para separar efetivamente as versões de desenvolvimento e produção do código, dando-lhes mais controle sobre quando suas alterações de código são implantadas na produção.
2.) Mudanças experimentais onde um grande número de desenvolvedores colabora durante vários meses, mas as mudanças têm o potencial de interromper a versão de produção. Ao mesmo tempo, a escala de colaboração é grande o suficiente para que não seja prático usar uma pilha muito grande de diferenças para simular uma ramificação.
3.) Desbloqueio migrações do Git. Mesmo que o objetivo final seja ter apenas uma ou algumas versões no monorepo Sapling, durante as migrações precisamos de um equivalente às ramificações Git para que a migração possa ser concluída e a consolidação possa ocorrer dentro do monorepo. Nem sempre é possível consolidar todas as filiais no Git antes de migrar para o monorepo.
É importante notar que ter uma única versão de código continua sendo a suposição padrão para o monorepo. No entanto, se qualquer um dos três motivos acima se aplicar, a ramificação de diretório pode ser usada como uma solução, fornecendo fluxos de trabalho ramificados sem sacrificar os benefícios de um monorepo.
Trabalho futuro com ramificação de diretório
Também estamos planejando aproveitar a ramificação de diretórios para melhor integração dos repositórios Git no monorepo Sapling. Mais especificamente, estamos desenvolvendo um mecanismo leve de migração de repositórios. Em vez de tomar uma decisão irreversível de submeter todos os commits do repositório Git no histórico do monorepo, criamos um link virtual para um repositório externo onde o Sapling pode carregar o histórico do Git dinamicamente quando o usuário solicitar. Isso reduz a barreira de entrada dos repositórios Git no monorepo e é útil para integrações antes de se comprometer com a migração do histórico completo. Isto será fornecido como uma opção para o importação de subárvore sl comando ao trabalhar com repositórios Git externos.
Fique ligado – publicaremos um artigo separado sobre este tópico assim que tivermos aprendizados suficientes para compartilhar.
Para saber mais sobre Meta Open Source, visite nosso siteassine nosso Canal do YouTubeou siga-nos no Facebook, Tópicos, X, Céu Azul e LinkedIn.
Agradecimentos
Várias pessoas nas organizações de controle de origem, experiência do desenvolvedor e código aberto da Meta contribuíram para o design e implementação da ramificação de diretório no Sapling. Gostaríamos de agradecer: Chris Cooper, George Giorgidze, Mark Juggurnauth-Thomas, Jon Janzen, Pingchuan Liu, Muir Manders, Mark Mendoza, Jun Wu e Zhaolong Zhu.
Também somos gratos ao Git, Mercuriale Jujutsu comunidades de código aberto para seus discussões relacionadas à ramificação na conferência GitMerge 2024 em Berlim. Esperamos que o código-fonte aberto do Sapling e os aprendizados compartilhados neste artigo beneficiem todos os sistemas de controle de origem.

Café Codificado é um portal dinâmico e confiável criado especialmente para desenvolvedores. Nosso foco é entregar:
Dicas práticas para programação, produtividade, frameworks, testes, DevOps e muito mais;
Notícias atualizadas, acompanhando tendências e lançamentos do mundo da tecnologia, compiladas com relevância e sem jargões desnecessários.
O que você encontra aqui:
Artigos objetivos e comandáveis — Tutoriais, tutoriais passo-a-passo e dicas que vão direto ao ponto.
Cobertura das tecnologias que estão em alta — do universo da IA, computação em nuvem e segurança à engenharia de software e criatividade em código.
Conteúdo para todos os níveis — de iniciantes buscando praticidade, a profissionais em busca de insights estratégicos e aperfeiçoamento.
Comunidade ativa — textos humanizados, perguntinhas instigantes e espaço para você contribuir com reflexões e comentários.