5 tips for CI-friendly Git repositories (and Git-friendly CI)

Set yourself up for success – it all starts with your repository.

Sarah Goff-Dupont Sarah Goff-Dupont

Como falei em outro artigo, Git e entrega contínua incluem uma das deliciosas combinações de "chocolate e manteiga de amendoim" que ocasionalmente encontramos no mundo do software – dois ótimos sabores que ficam excelentes juntos. Assim, queria compartilhar algumas dicas para fazer seus builds no Bamboo serem bem reproduzidos com seus repositórios do Bitbucket. A maior parte da interação acontece nas fases de build e de teste da entrega contínua. Assim, você me verá falando mais em termos de "CI", em vez de "CD".

1: Store large files outside your repo

Algo que você escuta com frequência sobre Git é que deve evitar colocar arquivos grandes em seu repositório: binários, arquivos de mídia, artefatos arquivados, etc. Isso porque, depois de adicionar um arquivo, ele sempre estará lá no histórico do repositório, o que significa que, sempre que o repositório for clonado, aquele arquivo enorme e pesado também será clonado.

Getting a file out of the repo’s history is tricky – it’s the equivalent of performing a frontal lobotomy on your code base. And this surgical file extraction alters the whole history of the repo, so you no longer have a clear picture of what changes were made and when. All good reasons to avoid large files as a general rule. And...

Manter arquivos grandes fora dos seus repositórios Git é especialmente importante para CI.

Sempre que você executa um build, seu servidor de CI precisa clonar seu repositório no diretório de build de trabalho. Se o seu repositório estiver repleto de artefatos enormes, ele tornará esse processo lento e aumentará o tempo que seus desenvolvedores precisarão esperar pelos resultados do build.

Ok, tudo bem. Mas e se o seu build depender de binários de outros projetos ou artefatos grandes? Essa é uma situação muito comum, e provavelmente sempre será. Assim, a pergunta é: como podemos lidar com ele de maneira eficaz?

An external storage system like Artifactory (who make an add-on for Bamboo), Nexus, or Archiva can help for artifacts that are generated by your team or the teams around you. The files you need can be pulled into the build directory at the beginning of your build – just like the 3rd-party libraries you pull in via Maven or Gradle.

 Pro tip: If the artifacts change frequently, avoid the temptation to sync your big files to the build server every night so you only have to transfer them across the disc at build time. In between your nightly syncs, you’ll end up building with stale versions of the artifacts. Plus, developers need these files for builds on their local workstations anyway. So overall, the cleanest thing to do is to just make artifact download part of the build.

If you don't already have an external storage system on your network, it's easiest to take advantage of Git large file support (LFS).

Git LSF é uma extensão que armazena ponteiros para arquivos grandes em seu repositório, em vez de armazenar os arquivos em si. Os arquivos em si são armazenados em um servidor remoto. Como você pode imaginar, isso reduz o tempo de clonagem drasticamente.

Git LSF

Chances are, you already have access to Git LFS – both Bitbucket and Github support it.

2: Usar clones superficiais para CI

Sempre que um build é executado, seu servidor de build clona o repositório no diretório de trabalho atual. Como mencionei antes, quando um Git clona um repositório, ele clona todo o histórico do repositório por padrão. Assim, ao longo do tempo, essa operação naturalmente levará cada vez mais tempo. A menos que você habilite a clonagem superficial no Bamboo.

Com clones superficiais, apenas o instantâneo atual do seu repositório será aberto. Assim, pode ser bastante útil para reduzir tempos de execução de build, especialmente ao trabalhar com repositórios grandes e/ou mais antigos.

Captura de tela de repositório Git

Digamos que seu build exija todo o histórico do repositório: se, por exemplo, uma das etapas no seu build atualizar o número de versão no seu POM (ou similar), ou você estiver mesclando dois branches a cada build. Ambos os casos exigem que o Bamboo envie as alterações por push de volta ao seu repositório.

As of Git 1.9, simple changes to files (like updating a version number) can be pushed without the entire history present. But merging still requires the repo's history because Git needs to look back and find the common ancestor of the two branches – that’s going to be a problem if your build uses shallow cloning. Which leads me to tip #3.

3: Armazenar em cache o repositório em agentes de build

This also makes the cloning operation much faster, and Bamboo actually does this by default.

Observe que o armazenamento em cache do repositório o beneficia apenas se você estiver usando agentes que persistam de um build para outro. Se você criar e destruir agentes de build em EC2 ou outro provedor de nuvem sempre que um build é executado, o armazenamento em cache do repositório não fará diferença, pois você estará trabalhando com um diretório de build vazio e precisará obter uma cópia completa do repositório de qualquer maneira.

Clones superficiais mais armazenamento em cache de repositório dividido pelos agentes elásticos vs. persistentes é igual a uma interessante rede de fatores. Aqui está uma pequena matriz para ajudá-lo a criar uma estratégia.

Persistent agents vs elastic agents screenshot

4: Escolha seus acionadores com sabedoria

It goes (almost) without saying that running CI on all your active branches is a good idea. But is it a good idea to run all builds on all branches against all commits? Probably not. Here’s why.

Let’s take Atlassian, for example. We have upwards of 800 developers, each pushing changes to the repo several times a day – mostly pushes to their feature branches. That’s a lot of builds. And unless you scale your build agents instantly and infinitely, it means a lot of waiting in the queue.

One of our internal Bamboo servers houses 935 different build plans. We plugged 141 build agents into this server, and used best practices like artifact passing and test parallelization to make each build as efficient as possible. And still: building after each and every push was clogging up the works.

Em vez de simplesmente montar outra instância do Bamboo com outros 100 agentes ou mais, recuamos e perguntamos se isso era realmente necessário. E a resposta foi negativa.

Dessa forma, demos aos desenvolvedores a opção de transformar seus builds de branch em botões, em vez de sempre acioná-los automaticamente. É uma boa maneira de equilibrar o rigor de teste com a preservação de recursos, e os branches são o local em que a maior parte da atividade está acontecendo. Por isso, é uma ótima oportunidade para economias.

Muitos desenvolvedores gostam de ter o maior controle que botões oferecem e consideram que se encaixam perfeitamente no fluxo de trabalho deles. Outros preferem não pensar sobre quando executar um build e atêm-se a acionadores automáticos. Ambas as abordagens podem funcionar. O importante é ter seus branches em teste em primeiro lugar e garantir que você tenha um build limpo antes de mesclar na cadeia produtiva.

Preferences

Porém, branches críticos, como de versão mestre e estável, são outra história. Builds são acionados automaticamente, seja sondando o repositório quanto a alterações ou enviando uma notificação push do Bitbucket para o Bamboo. Uma vez que usamos branches de desenvolvimento para todo nosso trabalho em andamento, as únicas confirmações que vêm para o mestre devem (em teoria) ser branches de desenvolvimento sendo mescladas. Além disso, essas são as linhas de código com base nas quais lançamos e criamos nossos branches de desenvolvimento. Assim, é muito importante termos resultados de teste em tempo hábil para cada merge.

5: Pare de sondar, comece a capturar

Polling your repo every few minutes looking for changes is a pretty cheap operation for Bamboo. But when you're scaling up to hundreds of builds against thousands of branches involving dozens of repos, it adds up fast. Instead of taxing Bamboo with all that polling, you can have Bitbucket call out when a change has been pushed and needs to be built.

Typically, this is done by adding a hook to your repository, but as it happens, the integration between Bitbucket and Bamboo does all the under-the-hood set-up for you. Once they're linked on the back end, repo-driven build triggers Just Work™ right out of the box. No hooks or special configs required.

Configure Bitbucket screenshot

Regardless of tooling, repo-driven triggers carry the advantage of automatically fading into the sunset when the target branch goes inactive. In other words, you’ll never waste your CI system’s CPU cycles polling hundreds of abandoned branches. Or waste your own time manually turning off branch builds. (Though it’s worth noting that Bamboo can easily be configured to ignore branches after X days of inactivity, if you still prefer polling.)

The key to using Git with CI is...

...simply being thoughtful. All the things that worked great when you were doing CI with a centralized VCS? Some of them will work less great with Git. So check your assumptions – that's the first step. For Atlassian customers, the second step is integrating Bamboo with Bitbucket. Check out our documentation for details, and happy building!