Automatizando Git com Python
Automatize tarefas do Git com Python usando GitPython e subprocess. Crie scripts para commits, branches, relatorios e fluxos CI/CD.
Git e a ferramenta de controle de versao mais utilizada no mundo, e Python pode automatizar praticamente qualquer fluxo de trabalho com ele. Desde gerar relatorios de commits ate criar hooks personalizados e gerenciar repositorios em massa, a combinacao de Python e Git e extremamente poderosa. Neste artigo, vamos explorar como automatizar Git usando GitPython e subprocess.
Instalando o GitPython
O GitPython e a biblioteca mais popular para interagir com repositorios Git em Python:
pip install gitpython
Vamos comecar com operacoes basicas:
from git import Repo
# Abrir repositorio existente
repo = Repo("/caminho/do/projeto")
# Informacoes basicas
print(f"Branch atual: {repo.active_branch.name}")
print(f"Ultimo commit: {repo.head.commit.hexsha[:8]}")
print(f"Autor: {repo.head.commit.author.name}")
print(f"Mensagem: {repo.head.commit.message.strip()}")
print(f"Tem alteracoes? {repo.is_dirty()}")
print(f"Arquivos nao rastreados: {repo.untracked_files}")
Automatizando Commits e Branches
Crie scripts para operacoes comuns de Git:
from git import Repo
from datetime import datetime
def commit_automatico(caminho_repo: str, mensagem: str = None):
"""Adiciona todas as alteracoes e faz commit."""
repo = Repo(caminho_repo)
if not repo.is_dirty() and not repo.untracked_files:
print("Nenhuma alteracao para commitar.")
return None
# Adicionar todas as alteracoes
repo.git.add(A=True)
# Gerar mensagem automatica se nao fornecida
if mensagem is None:
alterados = [item.a_path for item in repo.index.diff("HEAD")]
novos = repo.untracked_files
mensagem = f"auto: {len(alterados)} alterados, {len(novos)} novos [{datetime.now().strftime('%Y-%m-%d %H:%M')}]"
# Commit
commit = repo.index.commit(mensagem)
print(f"Commit realizado: {commit.hexsha[:8]} - {mensagem}")
return commit
def criar_branch_feature(caminho_repo: str, nome_feature: str):
"""Cria branch de feature a partir da main."""
repo = Repo(caminho_repo)
branch_nome = f"feature/{nome_feature}"
# Garantir que estamos na main atualizada
main = repo.heads.main
main.checkout()
repo.remotes.origin.pull()
# Criar e mudar para nova branch
nova_branch = repo.create_head(branch_nome)
nova_branch.checkout()
print(f"Branch criada e ativa: {branch_nome}")
return nova_branch
def listar_branches_remotas(caminho_repo: str) -> list[str]:
"""Lista todas as branches remotas."""
repo = Repo(caminho_repo)
repo.remotes.origin.fetch()
branches = []
for ref in repo.remotes.origin.refs:
branches.append(ref.name.replace("origin/", ""))
return branches
# Uso
commit_automatico("/caminho/do/projeto", "feat: adiciona validacao de email")
Gerando Relatorios de Commits
Analise o historico do repositorio para gerar relatorios uteis:
from git import Repo
from collections import defaultdict, Counter
from datetime import datetime, timedelta
def relatorio_semanal(caminho_repo: str, dias: int = 7) -> dict:
"""Gera relatorio dos commits dos ultimos N dias."""
repo = Repo(caminho_repo)
data_limite = datetime.now() - timedelta(days=dias)
commits_por_autor = defaultdict(list)
arquivos_alterados = Counter()
total_commits = 0
for commit in repo.iter_commits("main"):
data_commit = datetime.fromtimestamp(commit.committed_date)
if data_commit < data_limite:
break
total_commits += 1
autor = commit.author.name
commits_por_autor[autor].append({
"hash": commit.hexsha[:8],
"mensagem": commit.message.strip(),
"data": data_commit.strftime("%Y-%m-%d %H:%M"),
})
# Contar arquivos alterados
for arquivo in commit.stats.files:
arquivos_alterados[arquivo] += 1
# Montar relatorio
relatorio = {
"periodo": f"Ultimos {dias} dias",
"total_commits": total_commits,
"autores": {},
"top_arquivos": arquivos_alterados.most_common(10),
}
for autor, commits in commits_por_autor.items():
relatorio["autores"][autor] = {
"total": len(commits),
"commits": commits,
}
return relatorio
def imprimir_relatorio(relatorio: dict):
"""Imprime o relatorio formatado."""
print(f"Relatorio de Commits - {relatorio['periodo']}")
print(f"Total de commits: {relatorio['total_commits']}")
print()
print("Commits por autor:")
for autor, dados in relatorio["autores"].items():
print(f" {autor}: {dados['total']} commits")
for c in dados["commits"][:3]:
print(f" [{c['hash']}] {c['mensagem']}")
print("\nArquivos mais alterados:")
for arquivo, contagem in relatorio["top_arquivos"]:
print(f" {arquivo}: {contagem} alteracoes")
rel = relatorio_semanal("/caminho/do/projeto", dias=7)
imprimir_relatorio(rel)
Git Hooks com Python
Hooks sao scripts executados automaticamente em eventos do Git. Vamos criar hooks uteis em Python:
#!/usr/bin/env python3
"""
pre-commit hook: verifica qualidade do codigo antes do commit.
Salvar em .git/hooks/pre-commit e tornar executavel.
"""
import subprocess
import sys
def verificar_formatacao():
"""Verifica se o codigo esta formatado com black."""
resultado = subprocess.run(
["black", "--check", "."],
capture_output=True,
text=True,
)
if resultado.returncode != 0:
print("ERRO: Codigo nao formatado. Execute 'black .' antes do commit.")
print(resultado.stdout)
return False
return True
def verificar_linting():
"""Executa flake8 nos arquivos staged."""
resultado = subprocess.run(
["git", "diff", "--cached", "--name-only", "--diff-filter=ACM"],
capture_output=True,
text=True,
)
arquivos_python = [
f for f in resultado.stdout.strip().split("\n")
if f.endswith(".py")
]
if not arquivos_python:
return True
resultado = subprocess.run(
["flake8"] + arquivos_python,
capture_output=True,
text=True,
)
if resultado.returncode != 0:
print("ERRO: Problemas de linting encontrados:")
print(resultado.stdout)
return False
return True
def verificar_testes():
"""Executa testes rapidos."""
resultado = subprocess.run(
["python", "-m", "pytest", "tests/", "-x", "--timeout=30"],
capture_output=True,
text=True,
)
if resultado.returncode != 0:
print("ERRO: Testes falharam:")
print(resultado.stdout[-500:])
return False
return True
if __name__ == "__main__":
verificacoes = [
("Formatacao", verificar_formatacao),
("Linting", verificar_linting),
("Testes", verificar_testes),
]
falhou = False
for nome, funcao in verificacoes:
print(f"Verificando {nome}...", end=" ")
if funcao():
print("OK")
else:
print("FALHOU")
falhou = True
if falhou:
print("\nCommit bloqueado. Corrija os problemas acima.")
sys.exit(1)
print("\nTodas as verificacoes passaram!")
Gerenciamento em Massa de Repositorios
Para equipes com muitos repos, automatize operacoes em massa:
import os
from git import Repo, GitCommandError
from pathlib import Path
def atualizar_todos_repos(pasta_base: str):
"""Faz pull em todos os repositorios de uma pasta."""
resultados = {"sucesso": [], "erro": [], "limpo": []}
for item in Path(pasta_base).iterdir():
if not item.is_dir():
continue
git_dir = item / ".git"
if not git_dir.exists():
continue
try:
repo = Repo(str(item))
branch = repo.active_branch.name
if repo.is_dirty():
resultados["erro"].append(
f"{item.name}: alteracoes nao commitadas"
)
continue
origin = repo.remotes.origin
info = origin.pull()
if info[0].flags & info[0].HEAD_UPTODATE:
resultados["limpo"].append(item.name)
else:
resultados["sucesso"].append(f"{item.name} ({branch})")
except GitCommandError as e:
resultados["erro"].append(f"{item.name}: {e}")
except Exception as e:
resultados["erro"].append(f"{item.name}: {e}")
# Relatorio
print(f"Atualizados: {len(resultados['sucesso'])}")
for r in resultados["sucesso"]:
print(f" {r}")
print(f"\nJa atualizados: {len(resultados['limpo'])}")
if resultados["erro"]:
print(f"\nErros: {len(resultados['erro'])}")
for e in resultados["erro"]:
print(f" {e}")
atualizar_todos_repos("/home/dev/projetos/")
Usando Subprocess para Comandos Git Avancados
Quando o GitPython nao cobre um caso especifico, use subprocess:
import subprocess
import json
def git_log_json(caminho: str, limite: int = 20) -> list[dict]:
"""Obtem log do git em formato estruturado."""
formato = '{"hash": "%H", "autor": "%an", "email": "%ae", "data": "%ai", "mensagem": "%s"}'
resultado = subprocess.run(
["git", "-C", caminho, "log", f"--max-count={limite}", f"--format={formato}"],
capture_output=True,
text=True,
)
commits = []
for linha in resultado.stdout.strip().split("\n"):
if linha:
commits.append(json.loads(linha))
return commits
def buscar_em_historico(caminho: str, termo: str) -> list[str]:
"""Busca um termo em todo o historico de commits."""
resultado = subprocess.run(
["git", "-C", caminho, "log", "--all", f"--grep={termo}", "--oneline"],
capture_output=True,
text=True,
)
return resultado.stdout.strip().split("\n")
commits = git_log_json("/caminho/do/projeto", limite=10)
for c in commits:
print(f"[{c['hash'][:8]}] {c['autor']}: {c['mensagem']}")
Boas Praticas
Ao automatizar Git com Python, sempre trate excecoes do GitPython adequadamente. Nunca armazene credenciais no codigo; use SSH keys ou credential helpers. Teste seus scripts em repositorios de teste antes de aplica-los em producao. E documente os hooks e scripts para que toda a equipe entenda o fluxo automatizado.
Conclusao
Automatizar Git com Python elimina tarefas repetitivas e reduz erros humanos no fluxo de desenvolvimento. Desde hooks de pre-commit que garantem qualidade ate relatorios automaticos e gerenciamento em massa, as possibilidades sao vastas. Comece pelos hooks mais simples e evolua para automacoes completas conforme a necessidade da sua equipe.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português