540 palavras

Lefthook: o que rodar antes do commit e o que deixar para o push

, em Ruby, Lefthook

Já trabalhei em times em que o CI levava muito tempo para rodar. O problema não era só a espera. Alguém esquecia de rodar os testes na própria máquina, mandava o push, e o erro só aparecia depois que a suíte inteira tinha rodado no CI. Aí era corrigir, fazer push de novo e esperar mais uma vez.

O Lefthook resolveu isso para a gente. Ele gerencia git hooks, scripts que o git roda em momentos como antes de um commit ou antes de um push. Com ele, o teste que alguém esqueceria passa a rodar sozinho.

Instalando

Num projeto Ruby:

bundle add lefthook --group development
bundle exec lefthook install

O install cria os hooks dentro de .git/hooks. A configuração fica num lefthook.yml na raiz do projeto, versionado junto com o código, então todo mundo do time ganha os mesmos hooks.

O que rodar em cada momento

O erro mais comum é colocar tudo no commit. Se cada commit roda a suíte de testes, ele fica lento, e a primeira coisa que as pessoas aprendem é a pular o hook. A divisão que funcionou para mim:

pre-commit:
  jobs:
    - name: rubocop
      glob: "*.rb"
      run: bundle exec rubocop -a --force-exclusion {staged_files}
      stage_fixed: true

pre-push:
  jobs:
    - name: rspec
      run: bundle exec rspec

No commit, só o que é rápido. O RuboCop roda apenas nos arquivos .rb que estão no commit:

  • {staged_files} vira a lista desses arquivos;
  • o glob filtra só os .rb, em qualquer pasta;
  • o -a corrige o que dá para corrigir sozinho;
  • o stage_fixed: true coloca a correção dentro do próprio commit, sem você precisar rodar git add de novo;
  • o --force-exclusion faz o RuboCop respeitar o Exclude do .rubocop.yml. Sem ele, um arquivo excluído, como o db/schema.rb, é verificado quando aparece na lista.

No push, os testes. Rodar a suíte uma vez antes de subir o código é o ponto certo: o erro aparece na sua máquina em vez de aparecer no CI. Se algum teste falhar, o Lefthook interrompe o push.

A armadilha do {staged_files}

A primeira versão deste post tinha um exemplo assim:

pre-commit:
  commands:
    rspec:
      run: bundle exec rspec {staged_files}

A ideia era testar só os arquivos que mudaram. Mas o {staged_files} passa todos os arquivos do commit. Testando, o RSpec recebeu isto:

README.md app/models/user.rb lefthook.yml

E colocar um glob: "spec/**/*_spec.rb" também não resolve. Se você mudar só app/models/user.rb, nenhuma spec está no commit e o Lefthook pula os testes. Em nenhum dos dois casos ele testa o que você mudou. Por isso os testes ficam no pre-push, rodando a suíte inteira.

Quando precisar pular

Às vezes você quer fazer um commit de trabalho em andamento sem esperar nada:

LEFTHOOK=0 git commit -m "wip"

O git commit --no-verify também funciona. Isso também mostra o limite do Lefthook: ele ajuda o time a não esquecer, mas não garante nada. Quem garante é o CI, que continua rodando. A diferença é que agora ele quase sempre passa de primeira.

Se o install reclamar

Se você tiver um core.hooksPath global no git, o lefthook install se recusa a instalar e mostra as opções. O lefthook install --reset-hooks-path remove essa configuração, e o --force instala mesmo assim. Vale olhar o que o seu hook global faz antes de escolher.