Git mínimo viável para desenvolvimento baseado em tronco

Nov 30 2022
Menos é mais quando se trata de uma ferramenta poderosa – e supercomplicada – como o Git. Eli é cofundador e coCEO da trunk.io.

Menos é mais quando se trata de uma ferramenta poderosa – e supercomplicada – como o Git

Eli é co-fundador e co-CEO da trunk.io . Ele ocupou cargos de liderança em engenharia na Microsoft, Uber e Google (que adquiriu a startup que ele cofundou). Seu foco principal é ajudar as equipes a criar softwares melhores e mais rápidos.

Git Inferno de Dante

O desenvolvimento em um ambiente colaborativo é uma operação complicada que envolve o rastreamento e a coordenação do trabalho de vários desenvolvedores. Muitos de nós usamos Git para controle de versão para controlar todas essas mudanças. Mas, nas palavras do próprio Linus , o Git é “o gerenciador de informações infernal”. Ele oferece tantas opções e estados estranhos que você pode facilmente se encontrar em uma situação ruim a ponto de precisar clonar novamente do zero - mesmo que não tenha feito nada de errado.

Muitos de nós conhecemos um desenvolvedor que estudou o Git refspec e a árvore Merkle subjacente na qual ele se baseia, mas ser um Git savant provavelmente não está na descrição do seu trabalho. Posso dirigir um carro perfeitamente bem sem entender como a transmissão realmente funciona e, no final das contas, o Git é apenas um meio para um fim.

Para obter o máximo do Git, você deve usá-lo o mínimo possível quando se trata de desenvolvimento baseado em trunk. Limite os comandos que você usa, mantenha sua ramificação de recursos atualizada corretamente e padronize o uso como uma equipe. Individualmente, você pode desfrutar da liberdade de fazer o que quiser. Mas, como equipe, o preço da liberdade é pago com fricção.

Limite suas ações Git

A prática mais fácil de implementar para o pico de eficiência do Git é manter um subconjunto de comandos. Git pode fazer muitas coisas, mas a grande maioria de seu poder existe apenas para te ajudar a sair de situações terríveis. O número de comandos que o Git permite versus o número de comandos que você realmente deve invocar são bem diferentes.

Certa vez, um CTO me pediu para dar uma aula de git avançado para sua equipe de engenharia. Minha resposta foi simples:

no momento em que você precisa do git avançado - as rodas já saíram do carro. vamos nos concentrar em usar o core git corretamente.

Aqui estão os 10 comandos git que uso para fazer meu trabalho, minimizando o número de vezes que chego no inferno do Git:

Gerenciamento de Filiais

git checkout -t -b {branch-name}
Faça uma ramificação, defina o upstream para a ramificação atual e mude para ela. Você também pode fazer isso através do comando `git branch`, mas são necessários vários comandos. Como sempre quero mudar para um branch ao criá-lo, esse comando é mais sucinto. Crie e verifique o ramo de uma só vez. Eu costumo nomear todos os meus ramos eli/{work-being-done}.

git checkout {branch-name}
Mude para uma filial.

git branch -vv
Visualize suas ramificações atuais e seus upstreams.

git branch -D {branch-name}
Exclua uma ramificação local, geralmente para limpar ramificações obsoletas/mescladas na principal.

Empurrar e Puxar

git fetch origin main:main
Puxe o controle remoto mainpara o seu local main, mesmo quando você não estiver na mainfilial! Esse é o ; muito útil quando você está tentando mesclar o mais recente mainem uma ramificação de RP.

git push origin
Empurre meu código para o controle remoto; provavelmente girando muitas máquinas em CI para verificar meu trabalho.

git pull
Atualize minha ramificação local com o controle remoto de mesmo nome.

Comprometendo

git add -A .
Adicione tudo em que estou trabalhando (arquivos novos e editados).

git commit -am "work"
Veja aqui um mergulho profundo em meus pensamentos sobre mensagens locais de commit

git merge main
Muito mais sobre isso abaixo. Sou pragmático e não um rebaseador e fico feliz em confundir minha ramificação local com os acontecimentos de main.

Mantenha sua ramificação de recursos atualizada corretamente

Em meu último artigo , discuti como gerenciar o código de aterrissagem no main. Mas muitas vezes durante o desenvolvimento, e certamente antes da fusão, você precisa estar atualizado com as últimas alterações mainporque:

  • Você precisa de algum recurso que chegou mainpara fazer seu trabalho de recurso.
  • Algo mainmudou e se você não pegá-lo, você o quebrará ao tentar acessar seu código.
  • Você só quer se manter atualizado porque é engenheiro e adora coisas novas e brilhantes.

Porta 1: git merge

Esta é a minha estratégia de fusão preferida. É simples, confiável e sem frescuras. Um básico git mergepega todas as alterações que aconteceram maine as mescla como confirmações ao lado de suas alterações. Você pode ler ad nauseam sobre as desvantagens dessa solução sem frescuras, mas, honestamente, apenas dou de ombros e digo: "Não me importo com isso". Se você mesclar suas ramificações de trabalho main, todas essas bobagens sobre a poluição do git-log se tornarão apenas isso.

Honestamente, realmente não importa como/por que/o que entrou no meu ramo de trabalho. O que importa é - ele constrói, funciona e o código faz o que eu quero que ele faça? O resto é acadêmico.

Na prática, e eu sou um engenheiro muito prático, git mergepermite lidar com conflitos de mesclagem de forma limpa e voltar à construção. Você resolve todos os conflitos de mesclagem uma vez — e está pronto. Esta é a melhor opção para iniciantes, usuários avançados ocupados e aqueles que não sonham em se tornar gurus do Git.

Porta 2: git rebase

Esta é a pior opção de longe. OK eu sei; há praticamente toda uma geração que vê rebase vs merge algo como tabs vs espaços. Mas em projetos “reais”, onde uma ou várias equipes estão mesclando para um repo diariamente, o rebase requer a resolução de n conflitos de mesclagem em vez de um com uma mesclagem ou rebase compactado (mais sobre isso abaixo).

Além disso, qualquer rebase reescreve o histórico do Git, e isso significa que, se sua ramificação tiver uma contraparte remota, você precisará git push --forcesubstituir seu histórico com o novo histórico rebaseado de sua ramificação. Isso é bom em teoria, mas tem o potencial de excluir acidentalmente seu trabalho anterior de uma forma extremamente difícil de recuperar. É simplesmente muito mais perigoso do que a fusão e honestamente me dá dor de cabeça só de pensar nisso.

Para o propósito deste artigo, tentei fazer o rebase apenas para me lembrar o quanto não gosto desse fluxo de trabalho. Veja o que um simples comando rebase vomita em meu terminal:

bons fluxos de trabalho não devem exigir dicas para trabalhar

Há tanta coisa errada com esse terminal. Recebo anotações sobre hashes de confirmação — não quero vê-las. Recebo quatro linhas de dicas amarelas para me lembrar como tudo isso funciona. E agora estou em algum fluxo de trabalho apenas para obter o código da mainminha ramificação. A árvore de decisão é absurdamente complicada; git rebase --skip- o que é aquilo?! Por que seria bom pular um commit? Você sabe o que? Não me diga porque tenho trabalho a fazer.

Porta 3: git squash rebase

Você definitivamente não quer o que está atrás da porta nº 2, mas algumas pessoas são rebaseadoras obstinadas. Meu co-fundador, co-CEO David Apirian, me mostrou sua entrada secreta nos bastidores para rebase e é incrível. Chamaremos isso de rebase squash .

Reiterei a importância da simplicidade do Git, mas com essa abordagem, você pode ter seu rebase e comê-lo também. Você obtém as camadas mágicas de um rebase sem a insanidade de um fluxo de rebase padrão. Com um rebase de squash, você pode:

  • Veja uma comparação de seus commits locais antes de enviar para o GitHub.
  • Desfaça facilmente seu último commit.
  • Evite poluir a visão do GitHub de sua solicitação pull com uma tonelada de pequenos commits locais.

Etapa 1 — elimine todos os seus commits locais:

git reset --soft $(git merge-base HEAD main) &&
git commit -am "" --allow-empty-message

git rebase main

Você pode juntar tudo isso em um único uber-comando para extrair o main mais recente, esmagar todos os seus commits e rebasear no main mais recente:

git reset --soft $(git merge-base HEAD main) &&
git commit -am "" --allow-empty-message &&
git fetch origin main:main &&
git rebase main

[alias]
 smartcommit = !git add -A . && git commit -am "" --allow-empty-message
 squash = !git reset --soft $(git merge-base HEAD main) && git commit -am "" --allow-empty-message
 squashrebase = !git squash && git fetch origin main:main && git rebase main
 smart = !git smartcommit && git squashrebase

Um dos aspectos mais legais de sempre esmagar seus commits locais é que no topo do seu git logestá todo o trabalho que você fez no branch. Seu trabalho local agora se parece mais com a aparência do PR mesclado de squash quando chegar em main.

Você pode, a qualquer momento, remover o último commit do HEAD com sua ramificação local e ver todo o trabalho que você fez no main. Isso é semelhante à visão detalhada que o GitHub oferece do que mudou, mas permite que você permaneça em seu terminal e evite alternar confusamente entre duas UIs diferentes.

Devo admitir que, na primeira vez que David me mostrou esse fluxo de trabalho, fiquei impressionado. Ele praticamente matou o rebasedragão e a capacidade de visualizar localmente todos os arquivos que você alterou em seu ramo é super legal.

Embora o rebasing de squash ajude você a manter um melhor estado de fluxo, a esmagadora maioria dos desenvolvedores prefere apenas mesclar. O uso incorreto de rebase ou squash rebase resulta em atrito que mata a produtividade.

Armadilhas da ajuda da ramificação do GitHub

essas caixas de seleção no GitHub parecem úteis, mas não são

O GitHub oferece essa configuração ☝️ para sugerir a mesclagem mainautomática em sua ramificação se estiver desatualizada. Essa configuração geralmente é contraproducente. Quero que minha filial local seja a única fonte de verdade para meu PR, e ninguém mais (incluindo o GitHub) deve insistir nisso. Trabalhar com o GitHub em minha ramificação pessoal deve ser uma via de mão única. Eu codifico localmente e empurro.

Se você configurar o GitHub para começar a enviar atualizações para sua ramificação de trabalho, estará solicitando que o tráfego comece a ir na direção errada. E essa é uma receita para a dor.

Esse problema também surge quando os revisores de código deixam pequenas sugestões de confirmação em sua solicitação pull e você as aplica alegremente. Se você fizer qualquer trabalho em sua filial local antes de fazer essas alterações ... você abriu um pequeno saco de dor.

Muitas vezes descubro que acidentalmente fiz isso comigo mesmo quando tento enviar minhas atualizações de volta para o controle remoto e o git me diz que estou fora de sincronia. Nesse ponto, tenho duas opções:

  1. git push --forcee elimine qualquer coisa no controle remoto que não esteja em sua filial local. Geralmente qualquer coisa com a palavra força é destrutiva e desencorajada.
  2. git merge origin/{my-work-branch}para mesclar as alterações remotas na exibição local da ramificação.

Essa configuração do GitHub ️☝️ requer que os PRs estejam atualizados mainantes de se fundirem a ele. Isso geralmente “resolve” o potencial de ter um main; no entanto, ele só funciona com repos de velocidade muito baixa. Uma vez que um punhado de desenvolvedores tenta mesclar vários PRs por dia, eles acabam em uma corrida para mesclar main- ou, de outra forma, encontram-se constantemente realizando uma mesclagem / rebase mainem sua ramificação para estar "atualizado" novamente e basicamente competem com seus colegas de trabalho ser o primeiro a se fundir. Doloroso. Normalmente, se você deseja esse recurso, mas não é um desenvolvedor solo, provavelmente deseja uma fila de mesclagem - que é a maneira escalável de obter o principal sem interrupção. Isso é algo que achamos importante o suficiente para produzir com o Trunk Merge .

Minimize o Git para o valor máximo

Ao manter o uso do Git o mais simples possível, você dá aos desenvolvedores menos corda para se enforcar e mais tempo para fazer o trabalho de desenvolvimento que realmente importa. Faça o Git desempenhar um papel coadjuvante e não se torne o protagonista. Para manter um ambiente de desenvolvimento saudável e colaborativo, lembre-se de:

  • Escolha a menor área de superfície do Git para usar e fique com ela.
  • Sempre gerencie suas alterações upstream da mesma maneira.
  • Padronize as práticas em sua equipe para que seus colegas também não saiam dos trilhos.