494 lines
18 KiB
Bash
494 lines
18 KiB
Bash
#!/bin/bash
|
||
|
||
# 🔍 Branch Analyzer - Análise Completa de Branches e PRs
|
||
# Autor: Jonas Santos
|
||
# Versão: 1.0
|
||
# Data: Janeiro 2025
|
||
|
||
set -e
|
||
|
||
# Cores para output
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
PURPLE='\033[0;35m'
|
||
CYAN='\033[0;36m'
|
||
WHITE='\033[1;37m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Emojis
|
||
ROCKET="🚀"
|
||
CHECK="✅"
|
||
WARNING="⚠️"
|
||
INFO="ℹ️"
|
||
FIRE="🔥"
|
||
EYES="👀"
|
||
CHART="📊"
|
||
FILES="📁"
|
||
COMMIT="💻"
|
||
MERGE="🔀"
|
||
TEST="🧪"
|
||
SECURITY="🔒"
|
||
PERFORMANCE="⚡"
|
||
DOCS="📚"
|
||
|
||
# Função para exibir cabeçalho
|
||
print_header() {
|
||
echo -e "${WHITE}================================================================${NC}"
|
||
echo -e "${CYAN}${ROCKET} BRANCH ANALYZER - Análise Completa de Branch/PR${NC}"
|
||
echo -e "${WHITE}================================================================${NC}"
|
||
}
|
||
|
||
# Função para exibir seção
|
||
print_section() {
|
||
echo ""
|
||
echo -e "${YELLOW}$1${NC}"
|
||
echo -e "${WHITE}$(printf '%.0s-' {1..50})${NC}"
|
||
}
|
||
|
||
# Função para exibir erro e sair
|
||
error_exit() {
|
||
echo -e "${RED}❌ Erro: $1${NC}" >&2
|
||
exit 1
|
||
}
|
||
|
||
# Função para verificar se é um repositório git
|
||
check_git_repo() {
|
||
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||
error_exit "Este diretório não é um repositório Git."
|
||
fi
|
||
}
|
||
|
||
# Função para verificar se a branch existe
|
||
check_branch_exists() {
|
||
local branch=$1
|
||
if ! git show-ref --verify --quiet refs/remotes/origin/$branch; then
|
||
if ! git show-ref --verify --quiet refs/heads/$branch; then
|
||
error_exit "Branch '$branch' não encontrada localmente nem no remoto."
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Função para obter informações básicas da branch
|
||
get_branch_info() {
|
||
local branch=$1
|
||
local base_branch=${2:-main}
|
||
|
||
print_section "${INFO} INFORMAÇÕES BÁSICAS"
|
||
|
||
# Último commit
|
||
local last_commit=$(git log --format="%H" -n 1 origin/$branch 2>/dev/null || git log --format="%H" -n 1 $branch)
|
||
local last_commit_short=$(git log --format="%h" -n 1 origin/$branch 2>/dev/null || git log --format="%h" -n 1 $branch)
|
||
local author=$(git log --format="%an" -n 1 $last_commit)
|
||
local date=$(git log --format="%ad" --date=format:'%d/%m/%Y %H:%M' -n 1 $last_commit)
|
||
local message=$(git log --format="%s" -n 1 $last_commit)
|
||
|
||
echo -e "${CHECK} ${GREEN}Branch:${NC} $branch"
|
||
echo -e "${CHECK} ${GREEN}Último commit:${NC} $last_commit_short"
|
||
echo -e "${CHECK} ${GREEN}Autor:${NC} $author"
|
||
echo -e "${CHECK} ${GREEN}Data:${NC} $date"
|
||
echo -e "${CHECK} ${GREEN}Mensagem:${NC} $message"
|
||
|
||
# Status da branch
|
||
local ahead_behind=$(git rev-list --left-right --count origin/$base_branch...origin/$branch 2>/dev/null || echo "0 0")
|
||
local behind=$(echo $ahead_behind | cut -f1)
|
||
local ahead=$(echo $ahead_behind | cut -f2)
|
||
|
||
echo -e "${CHECK} ${GREEN}Commits à frente de $base_branch:${NC} $ahead"
|
||
echo -e "${CHECK} ${GREEN}Commits atrás de $base_branch:${NC} $behind"
|
||
|
||
if [ "$behind" -gt 0 ]; then
|
||
echo -e "${WARNING} ${YELLOW}Branch está desatualizada! Considere fazer rebase.${NC}"
|
||
fi
|
||
}
|
||
|
||
# Função para análise de commits
|
||
analyze_commits() {
|
||
local branch=$1
|
||
local base_branch=${2:-main}
|
||
|
||
print_section "${COMMIT} ANÁLISE DE COMMITS"
|
||
|
||
# Últimos commits da branch
|
||
echo -e "${EYES} ${BLUE}Últimos 10 commits:${NC}"
|
||
git log --oneline origin/$branch -10 2>/dev/null || git log --oneline $branch -10
|
||
|
||
# Commits únicos da branch
|
||
local unique_commits=$(git rev-list --count origin/$base_branch..origin/$branch 2>/dev/null || git rev-list --count $base_branch..$branch)
|
||
echo -e "\n${CHART} ${GREEN}Commits únicos nesta branch:${NC} $unique_commits"
|
||
|
||
if [ "$unique_commits" -gt 0 ]; then
|
||
echo -e "\n${EYES} ${BLUE}Commits únicos da branch:${NC}"
|
||
git log --oneline origin/$base_branch..origin/$branch 2>/dev/null || git log --oneline $base_branch..$branch
|
||
fi
|
||
}
|
||
|
||
# Função para análise de arquivos modificados
|
||
analyze_files() {
|
||
local branch=$1
|
||
local base_branch=${2:-main}
|
||
|
||
print_section "${FILES} ANÁLISE DE ARQUIVOS"
|
||
|
||
# Arquivos modificados
|
||
local modified_files=$(git diff --name-only origin/$base_branch origin/$branch 2>/dev/null || git diff --name-only $base_branch $branch)
|
||
local total_files=$(echo "$modified_files" | wc -l)
|
||
|
||
if [ -z "$modified_files" ]; then
|
||
echo -e "${INFO} ${BLUE}Nenhum arquivo modificado encontrado.${NC}"
|
||
return
|
||
fi
|
||
|
||
echo -e "${CHART} ${GREEN}Total de arquivos modificados:${NC} $total_files"
|
||
echo ""
|
||
echo -e "${EYES} ${BLUE}Arquivos modificados:${NC}"
|
||
echo "$modified_files" | while read file; do
|
||
if [ -n "$file" ]; then
|
||
echo " 📄 $file"
|
||
fi
|
||
done
|
||
|
||
# Estatísticas detalhadas
|
||
echo ""
|
||
echo -e "${CHART} ${GREEN}Estatísticas de mudanças:${NC}"
|
||
git diff --stat origin/$base_branch origin/$branch 2>/dev/null || git diff --stat $base_branch $branch
|
||
}
|
||
|
||
# Função para análise de tipos de arquivo
|
||
analyze_file_types() {
|
||
local branch=$1
|
||
local base_branch=${2:-main}
|
||
|
||
print_section "${CHART} ANÁLISE POR TIPO DE ARQUIVO"
|
||
|
||
local modified_files=$(git diff --name-only origin/$base_branch origin/$branch 2>/dev/null || git diff --name-only $base_branch $branch)
|
||
|
||
if [ -z "$modified_files" ]; then
|
||
return
|
||
fi
|
||
|
||
# Contar por extensão
|
||
echo -e "${EYES} ${BLUE}Distribuição por tipo:${NC}"
|
||
echo "$modified_files" | grep -E '\.' | sed 's/.*\.//' | sort | uniq -c | sort -nr | while read count ext; do
|
||
echo " 📋 .$ext: $count arquivo(s)"
|
||
done
|
||
|
||
# Arquivos críticos
|
||
echo ""
|
||
echo -e "${SECURITY} ${RED}Arquivos críticos detectados:${NC}"
|
||
|
||
local critical_found=false
|
||
echo "$modified_files" | while read file; do
|
||
if [ -n "$file" ]; then
|
||
case "$file" in
|
||
package.json|package-lock.json|yarn.lock)
|
||
echo " 🔴 $file (Dependências)"
|
||
critical_found=true
|
||
;;
|
||
*.config.js|*.config.ts|angular.json|tsconfig*.json)
|
||
echo " 🟡 $file (Configuração)"
|
||
critical_found=true
|
||
;;
|
||
*.env*|*secret*|*key*|*password*)
|
||
echo " 🔴 $file (Segurança - ATENÇÃO!)"
|
||
critical_found=true
|
||
;;
|
||
*test*|*spec*|*.test.ts|*.spec.ts)
|
||
echo " 🟢 $file (Testes)"
|
||
;;
|
||
*.md|docs/*|documentation/*)
|
||
echo " 📘 $file (Documentação)"
|
||
;;
|
||
esac
|
||
fi
|
||
done
|
||
}
|
||
|
||
# Função para análise de complexidade
|
||
analyze_complexity() {
|
||
local branch=$1
|
||
local base_branch=${2:-main}
|
||
|
||
print_section "${PERFORMANCE} ANÁLISE DE COMPLEXIDADE"
|
||
|
||
# Linhas adicionadas/removidas
|
||
local stats=$(git diff --numstat origin/$base_branch origin/$branch 2>/dev/null || git diff --numstat $base_branch $branch)
|
||
|
||
if [ -z "$stats" ]; then
|
||
echo -e "${INFO} ${BLUE}Nenhuma mudança detectada.${NC}"
|
||
return
|
||
fi
|
||
|
||
local total_added=0
|
||
local total_removed=0
|
||
local files_count=0
|
||
|
||
while read added removed file; do
|
||
if [ "$added" != "-" ] && [ "$removed" != "-" ]; then
|
||
total_added=$((total_added + added))
|
||
total_removed=$((total_removed + removed))
|
||
files_count=$((files_count + 1))
|
||
fi
|
||
done <<< "$stats"
|
||
|
||
echo -e "${CHART} ${GREEN}Linhas adicionadas:${NC} +$total_added"
|
||
echo -e "${CHART} ${RED}Linhas removidas:${NC} -$total_removed"
|
||
echo -e "${CHART} ${BLUE}Total de mudanças:${NC} $((total_added + total_removed))"
|
||
echo -e "${CHART} ${PURPLE}Arquivos afetados:${NC} $files_count"
|
||
|
||
# Classificação de complexidade
|
||
local total_changes=$((total_added + total_removed))
|
||
if [ $total_changes -lt 50 ]; then
|
||
echo -e "${CHECK} ${GREEN}Complexidade: BAIXA (< 50 linhas)${NC}"
|
||
elif [ $total_changes -lt 200 ]; then
|
||
echo -e "${WARNING} ${YELLOW}Complexidade: MÉDIA (50-200 linhas)${NC}"
|
||
elif [ $total_changes -lt 500 ]; then
|
||
echo -e "${WARNING} ${YELLOW}Complexidade: ALTA (200-500 linhas)${NC}"
|
||
else
|
||
echo -e "${FIRE} ${RED}Complexidade: MUITO ALTA (> 500 linhas)${NC}"
|
||
fi
|
||
|
||
# Arquivos com mais mudanças
|
||
echo ""
|
||
echo -e "${EYES} ${BLUE}Top 5 arquivos com mais mudanças:${NC}"
|
||
echo "$stats" | awk '$1 != "-" && $2 != "-" {print ($1+$2)" "$3}' | sort -nr | head -5 | while read changes file; do
|
||
echo " 📊 $file: $changes mudanças"
|
||
done
|
||
}
|
||
|
||
# Função para verificar conflitos potenciais
|
||
check_potential_conflicts() {
|
||
local branch=$1
|
||
local base_branch=${2:-main}
|
||
|
||
print_section "${MERGE} VERIFICAÇÃO DE CONFLITOS"
|
||
|
||
# Tentar fazer um merge dry-run
|
||
echo -e "${INFO} ${BLUE}Verificando possíveis conflitos...${NC}"
|
||
|
||
# Backup da branch atual
|
||
local current_branch=$(git branch --show-current)
|
||
|
||
# Criar uma branch temporária para teste
|
||
local temp_branch="temp-merge-test-$(date +%s)"
|
||
|
||
if git checkout -b $temp_branch origin/$base_branch >/dev/null 2>&1; then
|
||
if git merge --no-commit --no-ff origin/$branch >/dev/null 2>&1; then
|
||
echo -e "${CHECK} ${GREEN}Nenhum conflito detectado! Merge será limpo.${NC}"
|
||
git merge --abort >/dev/null 2>&1
|
||
else
|
||
echo -e "${WARNING} ${RED}Conflitos potenciais detectados!${NC}"
|
||
echo -e "${INFO} ${YELLOW}Arquivos com possíveis conflitos:${NC}"
|
||
git status --porcelain | grep "^UU\|^AA\|^DD" | while read status file; do
|
||
echo " ⚠️ $file"
|
||
done
|
||
git merge --abort >/dev/null 2>&1
|
||
fi
|
||
|
||
# Voltar para a branch original e limpar
|
||
git checkout $current_branch >/dev/null 2>&1
|
||
git branch -D $temp_branch >/dev/null 2>&1
|
||
else
|
||
echo -e "${WARNING} ${YELLOW}Não foi possível verificar conflitos automaticamente.${NC}"
|
||
fi
|
||
}
|
||
|
||
# Função para análise de testes
|
||
analyze_tests() {
|
||
local branch=$1
|
||
local base_branch=${2:-main}
|
||
|
||
print_section "${TEST} ANÁLISE DE TESTES"
|
||
|
||
local modified_files=$(git diff --name-only origin/$base_branch origin/$branch 2>/dev/null || git diff --name-only $base_branch $branch)
|
||
|
||
if [ -z "$modified_files" ]; then
|
||
return
|
||
fi
|
||
|
||
# Arquivos de teste modificados
|
||
local test_files=$(echo "$modified_files" | grep -E "\.(test|spec)\.(ts|js)$" || true)
|
||
local test_count=$(echo "$test_files" | grep -v '^$' | wc -l || echo 0)
|
||
|
||
echo -e "${CHART} ${GREEN}Arquivos de teste modificados:${NC} $test_count"
|
||
|
||
if [ "$test_count" -gt 0 ]; then
|
||
echo -e "${EYES} ${BLUE}Testes afetados:${NC}"
|
||
echo "$test_files" | while read file; do
|
||
if [ -n "$file" ]; then
|
||
echo " 🧪 $file"
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# Arquivos de código sem testes correspondentes
|
||
echo ""
|
||
echo -e "${WARNING} ${YELLOW}Verificando cobertura de testes:${NC}"
|
||
|
||
local code_files=$(echo "$modified_files" | grep -E "\.(ts|js)$" | grep -v -E "\.(test|spec|config|d)\.(ts|js)$" || true)
|
||
echo "$code_files" | while read file; do
|
||
if [ -n "$file" ]; then
|
||
local test_file1="${file%.ts}.spec.ts"
|
||
local test_file2="${file%.ts}.test.ts"
|
||
local test_file3="${file%.js}.spec.js"
|
||
local test_file4="${file%.js}.test.js"
|
||
|
||
if [ ! -f "$test_file1" ] && [ ! -f "$test_file2" ] && [ ! -f "$test_file3" ] && [ ! -f "$test_file4" ]; then
|
||
echo " ⚠️ $file (sem teste correspondente)"
|
||
fi
|
||
fi
|
||
done
|
||
}
|
||
|
||
# Função para sugestões de revisão
|
||
review_suggestions() {
|
||
local branch=$1
|
||
local base_branch=${2:-main}
|
||
|
||
print_section "${DOCS} SUGESTÕES PARA REVISÃO"
|
||
|
||
local modified_files=$(git diff --name-only origin/$base_branch origin/$branch 2>/dev/null || git diff --name-only $base_branch $branch)
|
||
local total_changes=$(git diff --numstat origin/$base_branch origin/$branch 2>/dev/null | awk '{added+=$1; removed+=$2} END {print added+removed}' || echo 0)
|
||
|
||
echo -e "${EYES} ${BLUE}Pontos importantes para revisar:${NC}"
|
||
|
||
# Checklist baseado na análise
|
||
echo ""
|
||
echo -e "${CHECK} ${GREEN}CHECKLIST DE REVISÃO:${NC}"
|
||
echo " 📋 Verificar se os commits têm mensagens claras"
|
||
echo " 📋 Validar se as mudanças atendem aos requisitos"
|
||
echo " 📋 Revisar lógica de negócio implementada"
|
||
echo " 📋 Verificar padrões de código e nomenclatura"
|
||
echo " 📋 Validar tratamento de erros"
|
||
echo " 📋 Checar performance das mudanças"
|
||
|
||
if echo "$modified_files" | grep -q -E "\.(test|spec)\.(ts|js)$"; then
|
||
echo " 📋 Executar testes automatizados"
|
||
echo " 📋 Verificar cobertura de testes"
|
||
else
|
||
echo -e " ${WARNING} Considerar adicionar testes"
|
||
fi
|
||
|
||
if echo "$modified_files" | grep -q "package.json"; then
|
||
echo -e " ${WARNING} Revisar mudanças em dependências"
|
||
echo " 📋 Verificar compatibilidade de versões"
|
||
fi
|
||
|
||
if echo "$modified_files" | grep -q -E "\.config\.(ts|js)$|angular\.json"; then
|
||
echo -e " ${WARNING} Revisar mudanças de configuração"
|
||
echo " 📋 Testar em diferentes ambientes"
|
||
fi
|
||
|
||
if [ "$total_changes" -gt 300 ]; then
|
||
echo -e " ${WARNING} PR grande - considerar quebrar em partes menores"
|
||
echo " 📋 Revisar com mais cuidado devido ao tamanho"
|
||
fi
|
||
|
||
echo ""
|
||
echo -e "${PERFORMANCE} ${PURPLE}COMANDOS ÚTEIS PARA REVISÃO:${NC}"
|
||
echo " git checkout $branch"
|
||
echo " git diff origin/$base_branch..origin/$branch"
|
||
echo " git log origin/$base_branch..origin/$branch --oneline"
|
||
echo " npm test (para executar testes)"
|
||
echo " npm run build (para verificar build)"
|
||
}
|
||
|
||
# Função para gerar relatório resumido
|
||
generate_summary() {
|
||
local branch=$1
|
||
local base_branch=${2:-main}
|
||
|
||
print_section "${ROCKET} RESUMO EXECUTIVO"
|
||
|
||
local modified_files=$(git diff --name-only origin/$base_branch origin/$branch 2>/dev/null || git diff --name-only $base_branch $branch)
|
||
local total_files=$(echo "$modified_files" | grep -v '^$' | wc -l || echo 0)
|
||
local total_commits=$(git rev-list --count origin/$base_branch..origin/$branch 2>/dev/null || git rev-list --count $base_branch..$branch)
|
||
local stats=$(git diff --numstat origin/$base_branch origin/$branch 2>/dev/null || git diff --numstat $base_branch $branch)
|
||
local total_added=$(echo "$stats" | awk '$1 != "-" {added+=$1} END {print added+0}')
|
||
local total_removed=$(echo "$stats" | awk '$2 != "-" {removed+=$2} END {print removed+0}')
|
||
local author=$(git log --format="%an" -n 1 origin/$branch 2>/dev/null || git log --format="%an" -n 1 $branch)
|
||
|
||
echo "┌─────────────────────────────────────────────────┐"
|
||
echo "│ RESUMO │"
|
||
echo "├─────────────────────────────────────────────────┤"
|
||
echo "│ Branch: $branch"
|
||
echo "│ Autor: $author"
|
||
echo "│ Base: $base_branch"
|
||
echo "│ Commits: $total_commits"
|
||
echo "│ Arquivos: $total_files"
|
||
echo "│ Linhas: +$total_added/-$total_removed"
|
||
echo "└─────────────────────────────────────────────────┘"
|
||
}
|
||
|
||
# Função principal
|
||
main() {
|
||
local branch_name=""
|
||
local base_branch="main"
|
||
local show_help=false
|
||
|
||
# Parse dos argumentos
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
-h|--help)
|
||
show_help=true
|
||
shift
|
||
;;
|
||
-b|--base)
|
||
base_branch="$2"
|
||
shift 2
|
||
;;
|
||
*)
|
||
if [ -z "$branch_name" ]; then
|
||
branch_name="$1"
|
||
fi
|
||
shift
|
||
;;
|
||
esac
|
||
done
|
||
|
||
# Mostrar ajuda
|
||
if [ "$show_help" = true ]; then
|
||
echo "🔍 Branch Analyzer - Análise Completa de Branches e PRs"
|
||
echo ""
|
||
echo "Uso: $0 [OPÇÕES] <nome-da-branch>"
|
||
echo ""
|
||
echo "Opções:"
|
||
echo " -h, --help Mostra esta ajuda"
|
||
echo " -b, --base BRANCH Define a branch base para comparação (padrão: main)"
|
||
echo ""
|
||
echo "Exemplos:"
|
||
echo " $0 feature/checkbox-vehicle"
|
||
echo " $0 feature/new-feature -b develop"
|
||
echo " $0 --help"
|
||
exit 0
|
||
fi
|
||
|
||
# Verificar se branch foi fornecida
|
||
if [ -z "$branch_name" ]; then
|
||
error_exit "Nome da branch é obrigatório. Use -h para ajuda."
|
||
fi
|
||
|
||
# Verificações iniciais
|
||
check_git_repo
|
||
check_branch_exists "$branch_name"
|
||
|
||
# Executar análises
|
||
print_header
|
||
get_branch_info "$branch_name" "$base_branch"
|
||
analyze_commits "$branch_name" "$base_branch"
|
||
analyze_files "$branch_name" "$base_branch"
|
||
analyze_file_types "$branch_name" "$base_branch"
|
||
analyze_complexity "$branch_name" "$base_branch"
|
||
check_potential_conflicts "$branch_name" "$base_branch"
|
||
analyze_tests "$branch_name" "$base_branch"
|
||
review_suggestions "$branch_name" "$base_branch"
|
||
generate_summary "$branch_name" "$base_branch"
|
||
|
||
echo ""
|
||
echo -e "${GREEN}${CHECK} Análise concluída com sucesso!${NC}"
|
||
echo ""
|
||
}
|
||
|
||
# Executar função principal com todos os argumentos
|
||
main "$@" |