Script para validar commits
O Conventional Commits é uma forma de padronização de commits dentro de um projeto de desenvolvimento de software, utilizando regras simples e claras com o objetivo de tornar os commits mais descritivos e padronizados, contribuindo para reduzir o tempo gasto em compreender como e por que algo foi feito em uma alteração ou correção posterior.
Neste post será apresentado de forma simples e objetiva um script em bash para validar a estrutura do commit seguindo o padrão do Conventional Commits.
Para saber mais acesse a publicação detalhada sobre o Conventional Commits
Validações
As validações realizadas pelo script antes de realizar o commit são as seguintes:
➤ Este script deve estar em um repositório git!
➤ O commit deve possuir opção (-am ou -m) e mensagem!
➤ O commit deve possuir uma das seguintes opções: -am, -m
➤ O commit deve estar no padrão: <tipo>[escopo opcional]: <descrição>
➤ O tipo do commit deve ser: feat, fix, chore, refactor, perf, style, test, docs, build, ci, revert
➤ O escopo do commit foi aberto de forma errada!
➤ O escopo do commit foi fechado de forma errada!
➤ O escopo do commit não deve ser vazio!
➤ A descrição do commit não deve ser menor que 10 caracteres! Tamanho atual: X caracteres.
➤ A descrição do commit não deve ser maior que 80 caracteres! Tamanho atual: X caracteres.
Ajuda
Breve descrição dos tipos permitidos em caso de dúvida antes de montar um commit:
Sucesso
Sempre que o commit estiver na estrutura correta, o commit será realizado com o seguinte retorno:
Info
Sempre que o commit estiver na estrutura correta, mas não houver alterações a seguinte informação é retornada:
Configurando o seu Linux para utilizar o comando
Primeiro é necessário criar um arquivo na pasta pessoal do usuário, neste caso o nome do arquivo é commitlint.scripts.py
. Após criar o arquivo, dentro do arquivo coloque o seguinte conteúdo, que é o script em Python que será chamado sempre que um commit for realizado:
import re, sys, subprocess
ERROR = "msg_error"
SUCCESS = "msg_success"
WARNING = "msg_warning"
DEFAULT = "msg_default"
def __exec(command: str) -> str:
"""Método responsável por executar um comando no terminal
Args:
command (str): Comando para executar
Returns:
str: Resultado do comando executado
"""
result = subprocess.run(
command,
shell=True,
capture_output=True,
encoding="utf8",
).stdout
return re.sub(r"\n$", "", result)
def __get_args() -> list:
"""Método responsável por capturar os argumentos informados
Returns:
list: Array com os argumentos
"""
# ignora o parametro da posição 0, pois é a URL do script
# ignora o parametro da posição 1, pois é o método chamado
return sys.argv[2 : len(sys.argv)]
def __msg(message: str, bold=True, type=ERROR, newLine=True):
"""Método responsável por formatar a mensagem antes de apresentar no console
Args:
message (str): Mensagem para exibição
bold (bool, optional): Determina se a mensagem será apresentada em negrito
type (str, optional): Tipo da mensagem (ERROR, SUCCESS, WARNING, DEFAULT). Defaults to ERROR
newLine (bool, optional): Nova linha antes de apresentar mensagem. Defaults to True
"""
result = "\n" if newLine else ""
if type == SUCCESS:
result += "✅ "
if type == WARNING:
result += "❕ "
if type == ERROR:
result += "❌ "
if bold:
message = __bold(message)
print(f"{result}{message}")
def __bold(text: str) -> str:
"""Retorna o texto em negrito
Args:
text (str): Texto para retornar em negrito
Returns:
str: Texto em negrito
"""
return f"\033[1m{text}\033[0m"
def commit_lint():
"""Método responsável por criar um novo commit validando antes se o commit está
no padrão do Conventional Commits (https://www.conventionalcommits.org/)
"""
# valida se o commit está sendo realizado dentro de um projeto GIT
if not __exec("git rev-parse --is-inside-work-tree"):
return __msg("Este script deve estar em um repositório git!")
args = __get_args()
type = args[0]
message = args[1]
if type == "-h":
return __msg(
__bold("Tipos de commits permitidos:")
+ "\n\tfeat > nova funcionalidade/recurso"
+ "\n\tfix > correção de bug"
+ "\n\tstyle > ajustes na apresentação do código"
+ "\n\trefactor > reestruturação do código"
+ "\n\tperf > melhoria no desempenho do código"
+ "\n\ttest > testes do código"
+ "\n\tdocs > atualização de documentação"
+ "\n\tbuild > configurações gerais do código"
+ "\n\tci > configurações de integração contínua"
+ "\n\tchore > manutenções no código"
+ "\n\trevert > reverter código"
+ "\nMais informações: https://schulz.net.br/blog/conventional-commits",
False,
WARNING,
False,
)
# valida se foi informado os argumentos
if len(args) != 2 or not args[0] or not args[1]:
return __msg("O commit deve possuir opção (-am ou -m) e mensagem!")
# valida se a estrutura do commit é válida
allowedTypes = ["-am", "-m"]
if type not in allowedTypes:
return __msg(
f"O commit deve possuir uma das seguintes opções: {', '.join(allowedTypes)}"
)
# valida se foi informado tipo e descricao
messageToValidate = message.split(": ", 1)
if len(messageToValidate) != 2:
return __msg(
__bold("O commit deve estar no padrão:")
+ "\n\t<tipo>[escopo opcional]: <descrição>"
+ "\n\n\t[corpo opcional]"
+ "\n\n\t[rodapé opcional]",
False,
)
# remove indicativo de breaking change se possuir, pois não necessita de validação
messageToValidate[0] = re.sub(r"!$", "", messageToValidate[0])
typeCommit = messageToValidate[0].split("(")
# cria um array com os tipos possiveis do commit de acordo com o Conventional Commits
typesCommit = [
"feat",
"fix",
"chore",
"refactor",
"perf",
"style",
"test",
"docs",
"build",
"ci",
"revert",
]
# valida o tipo do commit de acordo com o Conventional Commits
if typeCommit[0] not in typesCommit:
return __msg(f"O tipo do commit deve ser: {', '.join(typesCommit)}")
# se o tamanho do array for maior que 2, o escopo foi aberto mais de uma vez
if len(typeCommit) > 2:
return __msg("O escopo do commit foi aberto de forma errada!")
# validações se possui escopo
if len(typeCommit) == 2:
# valida se possui apenas um ")" e está no final do escopo
if typeCommit[1].count(")") != 1 or not typeCommit[1].endswith(")"):
return __msg("O escopo do commit foi fechado de forma errada!")
# valida se foi preenchido escopo
if typeCommit[1] == ")":
return __msg("O escopo do commit não deve ser vazio!")
# valida o tamanho da descricao do commit (ignora body e footer)
description = messageToValidate[1].split("\n")[0]
if len(description) < 10:
return __msg(
f"A descrição do commit não deve ser menor que 10 caracteres! Tamanho atual: {len(description)} caracteres."
)
if len(description) > 80:
return __msg(
f"A descrição do commit não deve ser maior que 80 caracteres! Tamanho atual: {len(description)} caracteres."
)
# ajusta as aspas e executa o commit
message = message.replace('"', "'")
result = __exec(f'git commit {type} "{message}"')
__msg(result, False, DEFAULT, False)
# realiza o tratamento do retorno
if result.find("file changed") != -1 or result.find("files changed") != -1:
return __msg("Commit realizado com sucesso!", True, SUCCESS)
if (
result.find("nothing to commit, working tree clean") != -1
or result.find("nothing added to commit") != -1
):
return __msg("Nenhuma informação para commitar!", True, WARNING)
# array de métodos que podem ser chamados fora do script
METHODS = ["commit_lint"]
if __name__ == "__main__":
try:
if sys.argv[1] not in METHODS:
raise Exception(
f"Não é possível chamar o método '{sys.argv[1]}' fora do script!"
)
# executa o método informado
globals()[sys.argv[1]]()
except Exception as e:
__msg(f"Erro inesperado: {str(e)}")
Adicionar o comando gc
Após criar o arquivo commitlint.scripts.py
na pasta pessoal do usuário, é necessário modificar o arquivo .bashrc
e criar um comando para validar os commits de acordo com o Conventional Commits.
O .bashrc é um arquivo de configuração usado pelo shell Bash, que é o interpretador de comandos padrão em muitos sistemas Unix e Linux.
Ele é executado toda vez que um terminal é aberto e permite diversas adições, como: criação de variáveis de ambiente, configurações do prompt do shell, criação de comandos e scripts personalizados.
Para modificar o arquivo .bashrc
utilizando o editor Vim, siga as instruções abaixo:
-
Abra um terminal.
-
Digite o seguinte comando para abrir o arquivo
.bashrc
:
vim ~/.bashrc
Use as teclas de navegação para mover o cursor até o final do arquivo e em seguida pressione a tecla i
para entrar no modo de edição.
No modo de edição adicione o seguinte código para criar um comando chamado gc
para validar os commits de acordo com o Conventional Commits:
# Criação de um commit com base no Conventional Commits
#
# $1 => -am ou -m
# $2 => Mensagem do commit
gc() {
python3 ~/commitlint.scripts.py commit_lint "$1" "$2"
}
Após finalizar as modificações no arquivo pressione a tecla Esc
para sair do modo de edição.
Em seguida digite :wq
(w=write, q=quit) e pressione a tecla Enter
para salvar as alterações e sair do Vim.
Com o comando criado, para que seja possível utilizar o comando é necessário reiniciar o terminal ou executar o comando source ~/.bashrc
para que as alterações entrem em vigor.
Após essas etapas, o comando gc
estará disponível no terminal para a criação de novos commits seguindo o padrão do Conventional Commits.