Configurando Git Hooks com Husky
Tags: javascript, husky, git, hooks
Published: 2020-08-09 21:08:23

## Sumário
1. [Introdução](#olá-pessoas)
2. [O que são Git Hooks?](#o-que-são-git-hooks)
3. [Quando esses Hooks são normalmente disparados?](#quando-esses-hooks-são-normalmente-disparados)
4. [O que é Husky?](#ta-mas-o-que-é-husky)
5. [Instalando o Husky](#então-bora-fazer)
6. [Criando o primeiro Hook com pre-commit](#criando-o-primeiro-hook-com-precommit)
7. [Hooks com pre-push](#o-problema-do-prepush)
8. [Criando o segundo Hook com pre-push](#criando-o-segundo-hook-com-prepush)
9. [Conclusão](#concluindo)

## Olá, pessoas!

Gostaria de compartilhar um pouco do que andei estudando sobre __Git Hooks__ com __Husky__ na minha primeira publicação aqui.

Antes de tudo, vamos contextualizar um pouco.

## O que são Git Hooks?

Segundo a [documentação oficial do Git](https://git-scm.com/book/pt-br/v2/Customizing-Git-Git-Hooks), Git Hooks são:
> __Scripts personalizados__ que podem ser disparados quando alguma ação importante acontece.

Esses Hooks/Scripts podem ser disparados tanto no lado do servidor quanto no lado do cliente.

### Quando esses Hooks são normalmente disparados?

No lado do cliente: quando ocorre alguma operação de __commit__ ou __merge__.
No lado do servidor: quando ocorre alguma operação de rede (como __push__, por exemplo).

Mas isso não é regra!

Você pode utilizar esses Hooks para qualquer ação que julgar importante e/ou necessária :)

A própria API do Git, por si só, permite a utilização desses Hooks independentemente de quais tecnologias estão sendo utilizadas no projeto.

Para projetos que utilizam o ecossistema Node/Npm (seja cliente ou servidor), pode-se utilizar o [Husky](https://github.com/typicode/husky) para ajudar na configuração desses Hooks.

Se quiser saber um pouco mais sobre o funcionamento desses Hooks por baixo dos panos, recomendo [este artigo](https://willianjusten.com.br/trabalhando-com-git-hooks-de-forma-facil/) maravilhoso do [Willian Justen](https://willianjusten.com.br/about/) :wink:

## Ta, mas o que é Husky?

~~É uma raça de cachorros peludos e fofos :dog:~~

De acordo com o [repositório oficial](https://github.com/typicode/husky#readme), Husky é uma biblioteca JavaScript que serve para previnir péssimos **git commit**, **git push** e mais!

Através do Husky, é possível configurar os Hooks no `package.json` do seu projeto e compartilhá-los com seu time.

Fazendo da forma padrão do Git, as configurações dos Hooks ficam dentro do diretório `.git` do projeto (que não é versionado), portanto só vai funcionar na sua máquina.

Aaaaah, então é por isso que utilizamos o Husky! Eu não sabia disso :scream: Descobri enquanto lia [este artigo](https://blog.cubos.io/melhorando-seu-workflow-com-husky-e-seus-git-hooks/) do [Cubos.io](https://cubos.io/).

A seguir, vamos ver como adicionar Hooks de **pre-commit** e **pre-push** com o Husky :zap::zap:

Obs: para adicionar o Husky e criar os Hooks, é importante que você possua um projeto Node/Npm com um arquivo `package.json` e o Git já inicializado :)

---

## Então, bora fazer!

A primeira a coisa a ser feita é instalar o Husky, que deve ficar nas dependências de desenvolvimento.
Para isso, execute o seguinte comando no diretório do seu projeto:

```bash
npm install husky --save-dev
```
ou
```bash
yarn add -D husky
```

Feito isso, você vai perceber que uma linha foi adicionada nas `devDependencies` do seu `package.json`, conforme exibido abaixo:

```json
  "devDependencies": {
    ...
    "husky": "^4.2.5"
  }
```

---

Você pode ver a lista e descrição de todos os Hooks disponíveis na [documentação oficial](https://git-scm.com/docs/githooks) (em inglês) ou [neste artigo](https://www.hostinger.com.br/tutoriais/como-usar-git-hooks/) do [Hostinger](https://www.hostinger.com.br/) (em português) :kissing::v:

Rapidamente falando, os Hooks que usaremos são:
`pre-commit` => é invocado quando um `git commit` é executado, antes da escrita do commit.
`pre-push` => é invocado quando um `git push` é executado, antes de enviar os commits.

Normalmente, esses Hooks são utilizados para rodar os `testes` e `lint` do projeto, então vamos pegar esses exemplos.

---

### Criando o primeiro Hook com pre-commit!

#### Obs: As configurações dos Hooks do Husky ficam em `husky.hooks` no `package.json`.

Vamos supor que você já tenha um `script` de `lint` que seja executado com `npm run lint` (ou `yarn lint`) e deseja rodá-lo sempre que o usuário faça uma ação de **commit**, permitindo ou bloqueando a escrita deste **commit**.

Neste caso, basta chamar o `npm run lint` (ou `yarn lint`) no Hook de `pre-commit` do Husky, conforme exemplificado abaixo:

```json
{
  "name": "my-project",
  "scripts": {
    ...
    "lint": "eslint . --ext .js,.jsx"
  },
  "devDependencies": {
    ...
    "husky": "^4.2.5"
  }
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint"
    }
  }
}
```

Caso queira adicionar outro `script` para ser executado no `pre-commit`, basta adicioná-lo na frente do `npm run lint` com um `&&` entre eles.
Exemplo:

```json
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint && npm run test"
    }
  }
```

Prontinho! Com isso, os dois `scripts` serão executados quando alguém tentar fazer um `commit` no projeto 😊

### O problema do pre-push

Como comentado anteriormente, o `pre-push` serve para executar um `script` antes de alguém fazer um `push` no projeto.

Mas, pesquisando um pouco sobre o `pre-push`, me deparei com [este artigo](https://medium.com/faun/why-using-pre-push-git-hooks-with-husky-is-not-always-a-good-idea-6233b8afcf83) do [Kacper Wdowik](https://medium.com/@kacperwdowik).

Basicamente, ele diz que nem sempre o `pre-push` é uma boa ideia, exemplificando isso com a execução dos testes do projeto.

Acontece que, normalmente, ao rodar `npm run test` é levado em consideração todos os arquivos modificados localmente no projeto; ou seja, considera arquivos/linhas que ainda não foram "commitados".

A dor que ele sentiu foi em relação à coerência e concordância dos testes com o que está, de fato, subindo. Um caso ~~não tão~~ incomum seria os testes passarem porque um arquivo foi corrigido mas não está nos arquivos para subir com o `push`, o que poderia resultar em um erro em produção ~~numa sexta-feira à noite~~, por exemplo :eyes:

Para resolver isso, ele fez uso da seguinte abordagem:  comparar o que está no `HEAD` do projeto com o que está em local, permitindo que a ação de `push` seja realizada apenas quando a pessoa realizou `commits` de todos os arquivos modificados, garantindo que o Hook rode com os arquivos que vão, de fato, subir.

Então você tem duas opções:

1. **Permitir** que as pessoas desenvolvedoras façam `push` quando alguma linha ou arquivo não esteja "commitado", sabendo que o `script` executado no `pre-push` **pode não estar em concordância** com o que está, de fato, subido;
2. **Impedir** que as pessoas desenvolvedoras façam `push` quando alguma linha ou arquivo não esteja "commitado" e ter a confiança de que o `script` executado no `pre-push` **esteja em concordância** com o que está, de fato, subindo.

Eu, pessoalmente, prefiro a segunda opção, mas isso vai de cada projeto :blush:

### Criando o segundo Hook com pre-push

Como comentado acima, vou seguir com a segunda abordagem, indo de acordo com o estudo do [Kacper Wdowik](https://medium.com/@kacperwdowik) no artigo [Why using pre-push Git Hooks with Husky is not always a good idea](https://medium.com/faun/why-using-pre-push-git-hooks-with-husky-is-not-always-a-good-idea-6233b8afcf83).

Ficaria algo assim:

```json
{
  "name": "my-project",
  "scripts": {
    ...
    "lint": "eslint . --ext .js,.jsx",
    "test": "npm run jest"
  },
  "devDependencies": {
    ...
    "husky": "^4.2.5"
  }
  "husky": {
    "hooks": {
      "pre-commit": "npm run lint",
      "pre-push": "git diff HEAD --quiet && npm run test && npm run lint"
    }
  }
}
```

Note que, no exemplo acima, o `HEAD` está sendo comparado com a sua árvore de trabalho atual. Caso sejam iguais, continua a execução. Caso contrário, termina a execução retornando um erro.

Utilizando esta abordagem, caso tente rodar um `git push` com arquivos modificados apenas localmente (sem "commitar"), você pode se deparar com uma mensagem de erro semelhante a esta:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/sex4y1se0dnzg5ipnrqm.png)

Caso isso aconteça, não siga a orientação dele de adicionar um `--no-verify` ao executar o `git push`, pois assim o Hook vai ser ignorado e seu esforço será em vão :sweat_smile:
Opte por "commitar" as modificações e refazer a operação de `push` ou seguir pela primeira abordagem comentada lá em cima (removendo o `git diff HEAD --quiet &&`).

Feito isso, seus Hooks de commit e push já vão estar funcionando para todo mundo que mexer no projeto :D

## Concluindo

No geral, os Git Hooks com Husky tendem a ficar mais simples e gerenciáveis, permitindo que mais de uma pessoa tenha acesso às mesmas regras.

Dei exemplos simples de implementação, chamando `scripts` já existentes no seu projeto. Mas você pode criar seus próprios `scripts` manualmente e chamá-los nos Hooks.

Pretendo falar um pouco sobre a criação de `scripts` manualmente em um artigo futuro, focado unicamente neste ponto, que tende a ser um pouco mais complexo e deixaria este artigo muito longo xD

Além do que foi falado aqui, recomendo o uso do [lint-staged](https://github.com/okonet/lint-staged) para facilitar a organização e visualização dos `scripts` de `lint` executados nos Hooks do Husky.

Se quiser saber um pouco mais sobre as vantagens de utilização do `lint-staged`, recomendo [este artigo](https://evellynlima.com.br/otimizando-o-pre-commit/) incrível da [Evellyn Lima](https://evellynlima.com.br/), onde ela fala sobre otimização do `pre-commit` e mostra exemplos práticos com `lint-staged`.

Ah, lembre-se que o Git possui vários Hooks que podem ser utilizados e cabe a você decidir quais Hooks fazem sentido (ou não) para aplicar no Workflow do seu projeto :wink:

Qualquer dúvida, sugestão, correção, comentário etc, me procure no [Twitter](https://twitter.com/helendiashd) ou no [Linkedin](https://www.linkedin.com/in/helendiashd/), que estou sempre disponível :v: