Files
DevGen/devtools/commitgen/cli.py
T

294 lines
9.4 KiB
Python

"""
Command-line interface for commitgen tool.
"""
import click
from rich.console import Console
from rich.prompt import Prompt
from rich.panel import Panel
from rich.table import Table
from rich.syntax import Syntax
from rich.progress import Progress, SpinnerColumn, TextColumn
from ..shared.config import Config
from .git import CommitGenGitService
from .generator import CommitGenerator
from ..changelog import ChangelogGenerator
from ..shared.git import GitService
import os
console = Console()
@click.group()
def cli():
"""Generate commit messages and changelogs using AI."""
pass
@cli.command()
@click.option(
"--files",
"-f",
multiple=True,
help="Specific files to generate commit messages for",
)
@click.option("--repo", "-r", help="Repository path")
@click.option("--commit", "-c", is_flag=True, help="Automatically commit changes")
@click.option("--push", "-p", is_flag=True, help="Push changes after commit")
@click.option(
"--conventional/--no-conventional",
default=True,
help="Use conventional commit format",
)
@click.option("--no-stage", is_flag=True, help="Skip automatic staging of changes")
@click.option("--sign", is_flag=True, help="Sign commits with GPG")
@click.option("--temperature", type=float, help="AI temperature (0.0-1.0)")
def generate(
files: tuple,
repo: str,
commit: bool,
push: bool,
conventional: bool,
no_stage: bool,
sign: bool,
temperature: float,
):
"""Generate commit messages for staged changes"""
try:
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
console=console,
) as progress:
# Initialize services
config = Config()
git_service = CommitGenGitService(config)
ai_service = CommitGenerator(config)
# Stage all changes if not disabled
if not no_stage:
task = progress.add_task("Staging changes...", total=None)
git_service.stage_all_changes()
progress.update(task, completed=True)
# Get staged changes
task = progress.add_task("Analyzing changes...", total=None)
staged_changes = git_service.get_staged_changes(files)
progress.update(task, completed=True)
if not staged_changes:
console.print("[yellow]No staged changes found.[/yellow]")
return
# Generate commit message
task = progress.add_task("Generating commit message...", total=None)
commit_message = ai_service.generate_commit_message(
staged_changes, temperature
)
progress.update(task, completed=True)
# Show preview and confirm
console.print("\n[bold]Generated commit message:[/bold]")
console.print(Panel(commit_message, title="Preview", border_style="blue"))
if commit:
task = progress.add_task("Committing changes...", total=None)
git_service.commit_changes(commit_message, sign=sign)
progress.update(task, completed=True)
if push:
task = progress.add_task("Pushing changes...", total=None)
git_service.push_changes()
progress.update(task, completed=True)
console.print("[green]Changes committed successfully![/green]")
else:
console.print(
"[yellow]Use --commit to apply the commit message.[/yellow]"
)
except Exception as e:
console.print(f"[red]Error: {str(e)}[/red]")
raise click.Abort()
@cli.group()
def changelog():
"""Generate and manage changelogs"""
pass
@changelog.command(name="generate")
@click.option(
"--version", "-v", required=True, help="Version number for the changelog entry"
)
@click.option("--from-tag", "-t", help="Generate changelog from this tag")
@click.option("--days", "-d", type=int, help="Generate changelog from the last N days")
@click.option(
"--commits", "-n", type=int, help="Generate changelog from the last N commits"
)
@click.option("--output", "-o", help="Output file path (default: CHANGELOG.md)")
@click.option("--temperature", type=float, help="AI temperature (0.0-1.0)")
def generate_log(
version: str,
from_tag: str,
days: int,
commits: int,
output: str,
temperature: float,
):
"""Generate a changelog from git history"""
try:
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
console=console,
) as progress:
# Initialize services
config = Config()
git_service = CommitGenGitService(config)
changelog_gen = ChangelogGenerator(config, git_service)
task = progress.add_task("Analyzing commit history...", total=None)
# Determine which method to use for getting commits
if from_tag:
entries = changelog_gen.get_commits(from_tag)
elif days:
entries = changelog_gen.get_commits_since_date(days)
elif commits:
entries = changelog_gen.get_last_n_commits(commits)
else:
# Default: get commits from the last 7 days
entries = changelog_gen.get_commits_since_date(7)
progress.update(task, completed=True)
if not entries:
console.print(
"[yellow]No conventional commits found in the specified range.[/yellow]"
)
return
task = progress.add_task("Generating changelog...", total=None)
output_path = output if output else "CHANGELOG.md"
changelog_gen.update_changelog_file(version, entries, output_path)
progress.update(task, completed=True)
console.print(
f"[green]Changelog generated successfully at {output_path}![/green]"
)
except Exception as e:
console.print(f"[red]Error: {str(e)}[/red]")
raise click.Abort()
@cli.group()
def config():
"""Manage configuration"""
pass
@config.command()
@click.option("--key", "-k", required=True, help="Configuration key")
@click.option("--value", "-v", required=True, help="Configuration value")
def set(key: str, value: str):
"""Set configuration value"""
try:
config = Config()
config.set(key, value)
console.print(f"[green]Configuration updated: {key}={value}[/green]")
except Exception as e:
console.print(f"[red]Error: {str(e)}[/red]")
raise click.Abort()
@config.command()
def show():
"""Show current configuration"""
try:
config = Config()
console.print(
Panel(
"\n".join(f"{k}: {v}" for k, v in config.get_all().items()),
title="[blue]Current Configuration[/blue]",
border_style="blue",
)
)
except Exception as e:
console.print(f"[red]Error: {str(e)}[/red]")
raise click.Abort()
@cli.command()
def status():
"""Show the current state of the repository."""
try:
# Initialize services
git = GitService(os.getcwd())
# Get repository info
repo_name = git.get_repo_name()
current_branch = git.get_current_branch()
# Get changes
staged_files = git.get_staged_files()
unstaged_files = git.get_unstaged_files()
# Create status table
table = Table(title=f"Repository Status: {repo_name} ({current_branch})")
table.add_column("Status", style="cyan")
table.add_column("Files", style="green")
# Add staged changes
if staged_files:
staged_content = "\n".join(f" • {file}" for file in staged_files)
table.add_row("Staged", staged_content)
else:
table.add_row("Staged", "No staged changes")
# Add unstaged changes
if unstaged_files:
unstaged_content = "\n".join(f" • {file}" for file in unstaged_files)
table.add_row("Unstaged", unstaged_content)
else:
table.add_row("Unstaged", "No unstaged changes")
# Show status
console.print("\n[bold]Repository Status:[/bold]")
console.print(table)
# Show diffs if there are changes
if staged_files or unstaged_files:
console.print("\n[bold]Changes:[/bold]")
# Show staged diffs
if staged_files:
console.print("\n[cyan]Staged Changes:[/cyan]")
for file in staged_files:
diff = git.get_diff(file, staged=True)
if diff:
console.print(Syntax(diff, "diff", theme="monokai"))
# Show unstaged diffs
if unstaged_files:
console.print("\n[yellow]Unstaged Changes:[/yellow]")
for file in unstaged_files:
diff = git.get_diff(file, staged=False)
if diff:
console.print(Syntax(diff, "diff", theme="monokai"))
except Exception as e:
console.print(f"[red]Error: {str(e)}[/red]")
raise click.Abort()
# Add command aliases
cli.add_command(generate, name="g")
cli.add_command(config, name="c")
cli.add_command(changelog, name="log")
if __name__ == "__main__":
cli()