DEV Community

Emanuel Braz
Emanuel Braz

Posted on

Git hooks em projetos Flutter 💙

Image description

Esse post é um complemento da Tech Talk que apresentei no dia 20/09/2022

Fala dev! E aí, tudo compilando certinho?

Neste story, eu vou mostrar como criar git hooks usando apenas Dart(ou outra linguagem de sua preferência), de uma maneira simples e sem usar nenhuma biblioteca. Eu também vou ensinar como criar teste funcional para seus scripts, como evitar commits com código quebrado, como rodar os testes antes de enviar código para o repositório, e como personalizar seu linter para não permitir commits que não estejam seguindo as guidelines do projeto.

Image description

// BEGIN TL;DR;

Git hooks são scripts executados automaticamente sempre que um determinado evento ocorre em um repositório Git. E utilizar esses gatilhos no seu projeto, pode ser uma boa maneira de melhorar ainda mais a qualidade das suas entregas.

Esses hooks são uma ótima oportunidade para começar a automatizar testes, documentação, formatação, linters e garantir a padronização do projeto, dando um boost na produtividade de seu time.

Disclaimer #1 Utilizarei um projeto Dart, mas você muito provavelmente, poderá adaptar para a sua linguagem preferida.

Disclaimer #2 Utilizarei comandos do Mac OS, por favor adapte para o sistema operacional que você estiver utilizando.

// END TL;DR;


Para começar, crie uma pasta hooks na raiz do seu projeto

$ mkdir hooks

Crie o arquivo para o nosso primeiro script, que será executado automaticamente antes de cada commit que você fizer.

$ touch hooks/pre-commit

Agora, vamos tornar esse arquivo executável, para que o git consiga rodar ele toda vez que um evento de pre-commit for disparado.

$ chmod +x hooks/pre-commit

Pronto, já temos o necessário para começar a escrever nossos scripts! Mas antes de começarmos, é muito IMPORTANTE que você configure o git para apontar para a pasta de hooks que você acabou de criar. Assim ele saberá onde encontrar todos os nossos scripts.

$ git config core.hooksPath hooks

P.S. Você precisa estar em um repositório git, caso não esteja, inicialize com o comando $ git init.

Vamos para o código?

Image description

A primeira coisa que precisamos fazer é usar uma shebang(#!) na primeira linha do arquivo, isso serve para dizer ao kernel qual será o interpretador a ser utilizado quando for executar os comandos presentes nos nossos hooks.

IMPORTANTE: Configure o dart, como nosso interpretador #!/usr/bin/env dart. O aplicativo dart deve estar previamente disponível e configurado no $PATH da sua máquina.

E defina nosso script, que irá fazer uma análise estática do código, antes de cada commit executado. Nosso arquivo ficará assim:

#!/usr/bin/env dart
// arquivo pre-commit

import 'dart:io';

main(List<String> arguments) async {
  print('* Analizando código dart, aguarde...\n');
  final process = await Process.run('dart', ['analyze'], runInShell: true);

  if (process.exitCode != 0) {
    print('* O código não é válido. verifique o erro!\n\n${process.stdout}');
  }

  exit(process.exitCode);
}
Enter fullscreen mode Exit fullscreen mode

Note que na linha 14 eu finalizei o script com o exitCode que foi gerado pelo processo dart analyze. Caso ele retorne zero, então o commit será executado logo em seguida, e se for qualquer outro número, o commit será abortado. Isso evitará que a gente comite código com erro.

Você não só pode, como deve fazer análise de código no seu pipeline remoto também. Mas o legal de automatizar local, é que você já sobe tudo bonitinho, fora que você terá feedbacks mais rápidos sobre eventuais problemas, e também utilizará menos recursos de cloud.

Vamos conhecer o próximo git hook?

Image description

Outra coisa que gosto de fazer é rodar os testes de unidade, antes de cada git push. Eu prefiro rodar os testes no push, porque os testes geralmente demoram um pouco mais, mas sinta-se livre para usar a sua imaginação e adaptar os hooks para as suas necessidades.

Para ganhar tempo, eu vou criar um hook de pre-push a partir do hook de pre-commit.

$ cp hooks/pre-commit hooks/pre-push

Irei trocar o parâmetro analyze por test, e quando os testes terminarem, irei dar um comando para o computador falar “os testes passaram” ou os “os testes falharam” dependendo da situação.

Onde eu trabalho todos os devs utilizam o mesmo OS, mas caso o seu time tenha sistemas operacionais diferentes, você sempre precisará considerar isso! Visto que alguns comandos podem ser diferentes para cada sistema operacional.

Para exemplificar, eu irei verificar se o sistema é Platform.isMacOS, e garantir que estou rodando um comando compatível. Veja como ficou o meu script abaixo:

#!/usr/bin/env dart
// arquivo pre-push

import 'dart:io';

main(List<String> arguments) async {
  print('* Executando os testes, aguarde...');
  final process = await Process.run('dart', ['test'], runInShell: true);
  print(process.stdout.toString());

  if (process.exitCode == 0) {
    if (Platform.isMacOS) {
      Process.runSync('say', ['os testes passaram']);
    } else if (Platform.isWindows) {
      // faz algo
    } else if (Platform.isLinux) {
      // faz algo
    }
  } else {
    if (Platform.isMacOS) {
      Process.runSync('say', ['os testes falharam']);
    } else if (Platform.isWindows) {
      // faz algo
    } else if (Platform.isLinux) {
      // faz algo
    }
  }

  exit(process.exitCode);
}
Enter fullscreen mode Exit fullscreen mode

Legal né? agora quando eu tentar fazer um push, os meus testes de unidade serão executados automaticamente, dá até para eu mudar de aba ou aplicativo, porque sei que o computador irá me avisar quando eles forem concluídos! Caso os testes falhem, nada será enviado para o repositório.

O seu time utiliza “Conventional Commits”?

O CC é uma boa prática, para deixar as mensagens de commits mais inteligíveis. Isso nos ajuda a manter um histórico de commits explícito; o que torna mais fácil escrever ferramentas automatizadas em cima deles.

Caso você não saiba o que isso significa, dá uma olhadinha nesse conteúdo: www.conventionalcommits.org

A primeira coisa que iremos fazer, será criar nosso hook commit-msg. Esse hook serve para a gente ter acesso ao arquivo que guarda a mensagem do commit no momento que tentarmos fazer um commit. Este hook sempre é executado depois do pre-commit e antes do post-commit, a não ser que um hook antecessor falhe e interrompa todo o processo.

$ cp hooks/pre-commit hooks/commit-msg

Caso você queira conhecer os outros hooks, utilize o comando abaixo, no seu terminal, para listar todos os hooks disponíveis.

$ man githooks

Neste momento iremos apenas verificar se a mensagem possui um dos possíveis prefixos aceitos no projeto(podemos evoluir isso em um próximo post), caso o contrário, não deixaremos fazer o commit e iremos printar quais são os prefixos permitidos no terminal.

#!/usr/bin/env dart
// arquivo commit-msg

import 'dart:io';

main(List<String> arguments) async {
  final commitMessage = (await File(arguments[0]).readAsString()).trim();
  final allowedPrefixes = [
    'build:',
    'chore:',
    'ci:',
    'docs:',
    'feat:',
    'fix:',
    'perf:',
    'refactor:',
    'revert:',
    'style:',
    'test:',
    'bump:'
  ];

  final hasPrefix = allowedPrefixes.any((prefix) => commitMessage.startsWith(prefix));

  if (!hasPrefix) {
    print('* O commit deve conter um prefixo válido.\n'
      'Os prefixos válidos são:\n\n'
      '${allowedPrefixes.join(" ")}\n'
    );
    exit(1);
  }
}
Enter fullscreen mode Exit fullscreen mode

Nesse hook escrevemos algumas regras importantes, que tal já criar testes funcionais para ele?

Image description

Primeiro crie a pasta test dentro da pasta hooks, então crie nosso primeiro arquivo de teste commit_msg_test.dart

$ mkdir hooks/test

$ touch hooks/test/commit_msg_test.dart

No arquivo de testes, teremos dois testes. Em ambos os casos iremos escrever uma mensagem de commit dentro do arquivo .git/COMMIT_EDITMSG, para garantir um cenário controlado.
Lembre-se, nunca devemos testar nem depender de recursos que estão além da própria unidade que estamos querendo testar.

import 'dart:io';

import 'package:test/test.dart';

void main() {
  test(
      'Dado que a mensagem de commit possui o prefixo "fix:", '
      'Quando o git hook commit-msg for disparado, '
      'Entao o programa deve retornar o exitCode 0', () async {
    // arrange
    ProcessResult result;
    int expected = 0;
    File('.git/COMMIT_EDITMSG').writeAsStringSync('fix: correcao de bug no login');

    // act
    result = await Process.run('hooks/commit-msg', ['.git/COMMIT_EDITMSG'], runInShell: true);
    final actual = result.exitCode;

    // assert
    expect(actual, equals(expected));
  });

  test(
      'Dado que a mensagem de commit não possui qualquer prefixo, '
      'Quando o git hook commit-msg for disparado, '
      'Entao o programa deve retornar o exitCode 1', () async {
    // arrange
    ProcessResult result;
    int expected = 1;
    File('.git/COMMIT_EDITMSG').writeAsStringSync('correcao de bug no login');

    // act
    result = await Process.run('hooks/commit-msg', ['.git/COMMIT_EDITMSG'], runInShell: true);
    final actual = result.exitCode;

    // assert
    expect(actual, equals(expected));
  });
}
Enter fullscreen mode Exit fullscreen mode

Rode os testes da pasta hooks/test/.

$ dart test hooks/test

E voilà, todos os testes passaram!

Image description


Top ein!? Agora a gente saberá facilmente caso o teammate ou a gente mesmo, quebre algum script. Quem sabe em um próximo post a gente evolui esses testes? Deixa no comentário se você quer ver mais sobre hooks, testes ou dar outra sugestão.

Vamos definir alguns combinados de time?

Combinados de times são artefatos importantes e essenciais para times ágeis e de alta performance. Esses combinados precisam fazer sentido para um conjunto de coisas, como a maturidade do time, projeto que está rodando, momento da empresa, entre outros.

Dentre tantos combinados que podemos ter, o Code Style é um dos, senão o mais comumente utilizado pelos devs.

Digamos por exemplo que estamos criando um package para publicar no pub.dev, e queremos evitar subir código com print(), já que sabemos que o método print irá expor informações para os usuários do nosso package.

Se você rodar um dart analyze ou flutter analyze no terminal, você terá o seguinte resultado:

Image description

Como você deve ter percebido, o linter acusou uma issue do tipo info, mas o tipo info não é o suficiente para abortar a execução, por isso, mesmo dando essa mensagem, ainda seria possível fazer commit do código contendo os prints.

Para mudar isso, precisamos dizer para o linter, que os prints devem ser considerados como erro! Além disso, é uma boa utilizar um conjunto de lints recomendados pela Google, e por esse motivo, vamos utilizar o flutter_lints.

No arquivo analysis_options.yaml adicione os vetores analyzer > errors e defina a chave/valor abaixo: avoid_print: error

include: package:flutter_lints/flutter.yaml

analyzer:
  errors:
    avoid_print: error
Enter fullscreen mode Exit fullscreen mode

Agora se eu rodar um flutter analyze novamente, teremos um erro como resultado.

Image description

Isso é o suficiente para abortar o commit, já que temos um hook de pre-commit que vai rodar um flutter analyze e abortar todo o processo em caso de erro.

Image description

Estamos chegando ao final do artigo, Ufaaa! Bora compartilhar os git hooks com o time?

Antes de mandar tudo para o staging area, vamos criar um arquivo chamado makefile, e usaremos ele pra facilitar com que o time configure o path dos git hooks em suas máquinas também.

Crie o arquivo makefile e crie os comandos install-hooks, uninstall-hooks e test-hooks.

$ touch makefile

O arquivo vai ficar assim:

install-hooks:
    @git config core.hooksPath hooks

uninstall-hooks:
    @git config --unset core.hooksPath

test-hooks:
    @dart test hooks/test
Enter fullscreen mode Exit fullscreen mode

Agora commita tudo, manda para o remoto e avisa pra galera rodar a instalação dos hooks na máquina deles também!

É só rodar o comando $ make install-hooks

Lembre-se sempre de usar o bom senso, tanto a escassez quanto o excesso de regras podem acabar atrapalhando mais do que ajudando, busque o equilíbrio, veja o que faz sentido para o seu time e beba bastante água.

Para saber mais sobre Git Hooks, recomendo o livro Pro Git, que está gratuito na Amazon.com na versão Kindle, e no site oficial, onde você poderá ler online ou baixar em vários formatos.

O projeto de exemplo está disponível no github.

That’s all folks!

Top comments (0)