Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 35 additions & 20 deletions src/gitauditor/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# --- Inicialização da Internacionalização (i18n) ---
import gettext
import json
import locale
import os

import typer
Expand All @@ -11,8 +12,6 @@
from rich.prompt import Prompt
from rich.table import Table

import locale

try:
system_lang = locale.getdefaultlocale()[0] or "en_US"
except Exception:
Expand All @@ -29,18 +28,21 @@
lang_to_use = cfg.get("lang", default_lang)
except Exception as e:
import sys

print(f"Aviso: Erro ao carregar config i18n: {e}", file=sys.stderr)

localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locales')
localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "locales")
try:
translate = gettext.translation('gitauditor', localedir, languages=[lang_to_use], fallback=True)
translate = gettext.translation("gitauditor", localedir, languages=[lang_to_use], fallback=True)
translate.install()
_ = translate.gettext
except Exception as e:
import sys

print(f"Aviso: Não foi possível carregar a tradução: {e}", file=sys.stderr)
import builtins
builtins.__dict__['_'] = lambda x: x

builtins.__dict__["_"] = lambda x: x
# ----------------------------------------------------

from gitauditor.commands.catalog_cmd import catalog_app
Expand All @@ -55,7 +57,7 @@
app = typer.Typer(
help=_("GitAuditor - O seu assistente IA e motor de políticas para repositórios Git."),
invoke_without_command=True,
epilog="Dica: Use [bold]gitauditor ui[/bold] para o menu interativo clássico."
epilog="Dica: Use [bold]gitauditor ui[/bold] para o menu interativo clássico.",
)

console = Console()
Expand Down Expand Up @@ -101,14 +103,16 @@ def run(self):
)
console.print("[0] 🚪 Sair")

total_pages = (total_filtered + self.page_size - 1) // self.page_size if total_filtered > 0 else 1
total_pages = (
(total_filtered + self.page_size - 1) // self.page_size if total_filtered > 0 else 1
)
if total_pages > 1:
console.print(f"[dim]Página {self.current_page + 1}/{total_pages} - Digite 'n' para próxima, 'p' para anterior[/dim]")
console.print(
f"[dim]Página {self.current_page + 1}/{total_pages} - Digite 'n' para próxima, 'p' para anterior[/dim]"
)

choices = [str(i) for i in range(10)] + ["n", "N", "p", "P"]
choice = Prompt.ask(
"Escolha uma opção", choices=choices
).lower()
choice = Prompt.ask("Escolha uma opção", choices=choices).lower()

if choice == "0":
console.print("[bold green]Até logo![/bold green] 👋")
Expand All @@ -125,6 +129,7 @@ def run(self):
self.current_page = total_pages - 1
elif choice == "1":
from gitauditor.commands.repo_cmd import handle_repo_details

handle_repo_details(self)
elif choice == "2":
from gitauditor.commands.catalog_cmd import open_repo
Expand Down Expand Up @@ -221,9 +226,7 @@ def _show_ai_menu(self):
repo_idx = Prompt.ask("Digite o ID do repositório")
if repo_idx.isdigit() and 0 <= int(repo_idx) < len(self.repos):
repo_path = self.repos[int(repo_idx)]
limit = Prompt.ask(
"Quantos commits analisar? (0 para todos)", default="0"
)
limit = Prompt.ask("Quantos commits analisar? (0 para todos)", default="0")
changelog_command(path=repo_path, limit=int(limit))
else:
console.print("[red]ID inválido![/red]")
Expand Down Expand Up @@ -296,9 +299,7 @@ def _load_catalog(self, silent=False):
console.print(
"[yellow]Por favor, apague o banco antigo e ressincronize:[/yellow]"
)
console.print(
"rm ~/.gitauditor/catalog.db && gitauditor catalog sync\n"
)
console.print("rm ~/.gitauditor/catalog.db && gitauditor catalog sync\n")
raise typer.Exit(1)
else:
raise
Expand Down Expand Up @@ -445,9 +446,7 @@ def _action_filter_table(self):
console.print("[3] Apenas Negados 🔴")
console.print("[4] Apenas Locais 📁")

choice = Prompt.ask(
"Escolha o filtro", choices=["1", "2", "3", "4"], default="1"
)
choice = Prompt.ask("Escolha o filtro", choices=["1", "2", "3", "4"], default="1")
if choice == "1":
self.current_filter = "Todos"
elif choice == "2":
Expand All @@ -470,6 +469,7 @@ def _action_filter_table(self):

app.command(name="config", help=_("Configurações do GitAuditor"))(config_command)


class AppState:
def __init__(self):
self._cli: GitAuditorCLI | None = None
Expand All @@ -480,60 +480,75 @@ def cli(self) -> GitAuditorCLI:
self._cli = GitAuditorCLI()
return self._cli


@app.callback(invoke_without_command=True)
def main_callback(ctx: typer.Context):
if ctx.obj is None:
ctx.obj = AppState()
if ctx.invoked_subcommand is None:
ctx.obj.cli.run()


@app.command()
def ui(ctx: typer.Context):
"""Modo Interativo (UI/Launcher Clássico)."""
if ctx.obj is None:
ctx.obj = AppState()
ctx.obj.cli.run()


@app.command(name="sync", hidden=True)
def sync_shortcut():
"""Alias para catalog sync"""
from gitauditor.commands.catalog_cmd import sync_catalog

sync_catalog()


@app.command(name="health", hidden=True)
def health_shortcut():
"""Alias para catalog health"""
from gitauditor.commands.catalog_cmd import health_dashboard

health_dashboard()


@app.command(name="history", hidden=True)
def history_shortcut(limit: int = 20):
"""Alias para policy log"""
from gitauditor.commands.policy_cmd import policy_log

policy_log(limit=limit)


@app.command(name="amend", hidden=True)
def amend_shortcut(ctx: typer.Context):
"""Alias para repo amend"""
from gitauditor.commands.repo_app import repo_amend

if ctx.obj is None:
ctx.obj = AppState()
repo_amend(ctx)


@app.command(name="details", hidden=True)
def details_shortcut(ctx: typer.Context):
"""Alias para repo details"""
from gitauditor.commands.repo_app import repo_details

if ctx.obj is None:
ctx.obj = AppState()
repo_details(ctx)


@app.command(name="review", hidden=True)
def review_shortcut(path: str = ".", staged: bool = False):
"""Alias para repo review"""
from gitauditor.commands.review_cmd import review_command

review_command(path=path, staged=staged)


@app.command(name="ssh", help=_("Gerenciar Chaves e Identidades SSH."))
def ssh_cmd(ctx: typer.Context):
if ctx.obj is None:
Expand Down
64 changes: 33 additions & 31 deletions src/gitauditor/commands/amend_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,7 @@ def handle_ai_amend(cli):

console.print("\n[bold yellow]Ações de Revisão e Paginação:[/bold yellow]")
console.print("[1] 🎯 Revisar um único commit específico")
console.print(
"[2] 🔄 Revisão Sequencial em Lote (O Mais Recente -> O Mais Antigo)"
)
console.print("[2] 🔄 Revisão Sequencial em Lote (O Mais Recente -> O Mais Antigo)")

valid_choices = ["0", "1", "2"]
if end < len(commits):
Expand Down Expand Up @@ -96,9 +94,7 @@ def handle_ai_amend(cli):
f"[dim]Hash original: {target_commit['hash']}\nMensagem atual: {target_commit['message']}[/dim]\n"
)

success = _process_single_amend(
cli, repo_path, target_commit, is_batch=True
)
success = _process_single_amend(cli, repo_path, target_commit, is_batch=True)
if not success:
break
console.print(
Expand All @@ -119,9 +115,7 @@ def handle_ai_amend(cli):
try:
repo = git.Repo(repo_path)
repo.git.push("--force-with-lease")
console.print(
"[bold green]✅ Push executado com sucesso![/bold green]"
)
console.print("[bold green]✅ Push executado com sucesso![/bold green]")
except Exception as e:
console.print(f"[bold red]Erro ao fazer push:[/] {e}")

Expand All @@ -130,22 +124,16 @@ def handle_ai_amend(cli):
console.print("[red]ID inválido![/red]")


def _process_single_amend(
cli, repo_path: str, target_commit: dict, is_batch: bool = False
) -> bool:
def _process_single_amend(cli, repo_path: str, target_commit: dict, is_batch: bool = False) -> bool:
"""Processa um commit individualmente. Retorna False se o usuário cancelar o lote inteiro."""
commit_hash = target_commit["hash"]

with console.status(
f"[bold green]Isolando diff do commit {commit_hash}..."
) as status:
with console.status(f"[bold green]Isolando diff do commit {commit_hash}...") as status:
diff = GitService.extract_diff_for_commit(repo_path, commit_hash)
if not diff or diff.startswith("Não foi possível"):
from rich.markup import escape

console.print(
f"[red]Falha ao isolar diff para o commit {commit_hash}.[/red]"
)
console.print(f"[red]Falha ao isolar diff para o commit {commit_hash}.[/red]")
console.print(f"[yellow]{escape(diff[:1000]) if diff else ''}[/yellow]")
return True # Retorna True para não cancelar o lote inteiro

Expand All @@ -165,9 +153,7 @@ def _process_single_amend(
return True

if is_batch:
prompt_text = (
"Deseja aplicar? [S]im / [N]Pular / [E]ditar manual / [C]ancelar Lote"
)
prompt_text = "Deseja aplicar? [S]im / [N]Pular / [E]ditar manual / [C]ancelar Lote"
choices = ["S", "N", "E", "C", "s", "n", "e", "c"]
else:
prompt_text = "Deseja aplicar esta mensagem e reescrever o histórico? (S/N)"
Expand All @@ -177,21 +163,27 @@ def _process_single_amend(

if confirm == "S":
try:
with console.status(
"[bold yellow]Iniciando Rebase Interativo Automático..."
):
with console.status("[bold yellow]Iniciando Rebase Interativo Automático..."):
backup_branch = GitService.reword_commit(repo_path, commit_hash, suggestion)
console.print(
"[bold green]✅ Commit atualizado com sucesso via rebase![/bold green]"
)
console.print("[bold green]✅ Commit atualizado com sucesso via rebase![/bold green]")
console.print(f"[dim]Backup guardado na branch: {backup_branch}[/dim]")

if Prompt.ask("Deseja DESFAZER (Rollback) essa reescrita?", choices=["S", "N", "s", "n"], default="N").upper() == "S":
if (
Prompt.ask(
"Deseja DESFAZER (Rollback) essa reescrita?",
choices=["S", "N", "s", "n"],
default="N",
).upper()
== "S"
):
GitService.rollback_amend(repo_path, backup_branch)
console.print("[yellow]Rollback executado com sucesso! Histórico restaurado.[/yellow]")
console.print(
"[yellow]Rollback executado com sucesso! Histórico restaurado.[/yellow]"
)
else:
# Se aceitou, podemos limpar a branch de backup para não sujar o repo
import git

try:
repo = git.Repo(repo_path)
repo.delete_head(backup_branch, force=True)
Expand All @@ -211,11 +203,21 @@ def _process_single_amend(
)
console.print(f"[dim]Backup guardado na branch: {backup_branch}[/dim]")

if Prompt.ask("Deseja DESFAZER (Rollback) essa reescrita?", choices=["S", "N", "s", "n"], default="N").upper() == "S":
if (
Prompt.ask(
"Deseja DESFAZER (Rollback) essa reescrita?",
choices=["S", "N", "s", "n"],
default="N",
).upper()
== "S"
):
GitService.rollback_amend(repo_path, backup_branch)
console.print("[yellow]Rollback executado com sucesso! Histórico restaurado.[/yellow]")
console.print(
"[yellow]Rollback executado com sucesso! Histórico restaurado.[/yellow]"
)
else:
import git

try:
repo = git.Repo(repo_path)
repo.delete_head(backup_branch, force=True)
Expand Down
16 changes: 4 additions & 12 deletions src/gitauditor/commands/audit_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ def handle_audit_duplicates(cli):
console.print("[yellow]Nenhum repositório para auditar.[/yellow]")
return

console.print(
Panel.fit("[bold magenta]🧹 Auditoria de Duplicados e Branches[/bold magenta]")
)
console.print(Panel.fit("[bold magenta]🧹 Auditoria de Duplicados e Branches[/bold magenta]"))

url_map = defaultdict(list)
for path in cli.repos:
Expand All @@ -63,9 +61,7 @@ def handle_audit_duplicates(cli):
return

for set_idx, (url, paths) in enumerate(duplicate_sets):
console.print(
f"\n[bold red]⚠️ Set [{set_idx}] - Duplicados para o remote:[/] {url}"
)
console.print(f"\n[bold red]⚠️ Set [{set_idx}] - Duplicados para o remote:[/] {url}")

dup_table = Table(show_header=True, header_style="bold yellow")
dup_table.add_column("Path ID", style="dim", width=7)
Expand Down Expand Up @@ -130,16 +126,12 @@ def handle_audit_duplicates(cli):
shutil.move(target_path, final_dest)
cli.repos.remove(target_path)
cli.repos.append(final_dest)
cli.repo_status[final_dest] = cli.repo_status.pop(
target_path, "⚪"
)
cli.repo_status[final_dest] = cli.repo_status.pop(target_path, "⚪")
console.print(
"[bold green]✅ Repositório unificado e movido com sucesso![/bold green]"
)
except Exception as e:
console.print(
f"[bold red]Erro ao mover repositório:[/] {e}"
)
console.print(f"[bold red]Erro ao mover repositório:[/] {e}")
else:
console.print("[red]Diretório destino inválido![/red]")
else:
Expand Down
Loading
Loading