fix: resolve CI build failures
This commit is contained in:
@@ -1,343 +1,44 @@
|
||||
"""CLI interface for Git Commit AI."""
|
||||
|
||||
import sys
|
||||
|
||||
import click
|
||||
|
||||
from git_commit_ai.core.cache import CacheManager, get_cache_manager
|
||||
from git_commit_ai.core.config import Config, get_config
|
||||
from git_commit_ai.core.conventional import (
|
||||
ConventionalCommitParser,
|
||||
ConventionalCommitFixer,
|
||||
validate_commit_message,
|
||||
)
|
||||
from git_commit_ai.core.git_handler import GitError, GitHandler, get_git_handler
|
||||
from git_commit_ai.core.ollama_client import OllamaClient, OllamaError, get_client
|
||||
|
||||
from git_commit_ai.core.git_handler import get_staged_changes, get_commit_history
|
||||
from git_commit_ai.core.ollama_client import generate_commit_message
|
||||
from git_commit_ai.core.prompt_builder import build_prompt
|
||||
from git_commit_ai.core.conventional import validate_conventional, fix_conventional
|
||||
from git_commit_ai.core.config import load_config
|
||||
|
||||
@click.group()
|
||||
@click.option(
|
||||
"--config",
|
||||
type=click.Path(exists=True, dir_okay=False),
|
||||
help="Path to config.yaml file",
|
||||
)
|
||||
@click.pass_context
|
||||
def main(ctx: click.Context, config: str) -> None:
|
||||
"""Git Commit AI - Generate intelligent commit messages with local LLM."""
|
||||
ctx.ensure_object(dict)
|
||||
cfg = get_config(config) if config else get_config()
|
||||
ctx.obj["config"] = cfg
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option(
|
||||
"--conventional/--no-conventional",
|
||||
default=None,
|
||||
help="Generate conventional commit format messages",
|
||||
)
|
||||
@click.option(
|
||||
"--model",
|
||||
default=None,
|
||||
help="Ollama model to use",
|
||||
)
|
||||
@click.option(
|
||||
"--base-url",
|
||||
default=None,
|
||||
help="Ollama API base URL",
|
||||
)
|
||||
@click.option(
|
||||
"--interactive/--no-interactive",
|
||||
default=None,
|
||||
help="Interactive mode for selecting messages",
|
||||
)
|
||||
@click.option(
|
||||
"--show-diff",
|
||||
is_flag=True,
|
||||
default=None,
|
||||
help="Show the diff being analyzed",
|
||||
)
|
||||
@click.option(
|
||||
"--auto-fix",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Auto-fix conventional commit format issues",
|
||||
)
|
||||
@click.pass_obj
|
||||
def generate(
|
||||
ctx: dict,
|
||||
conventional: bool | None,
|
||||
model: str | None,
|
||||
base_url: str | None,
|
||||
interactive: bool | None,
|
||||
show_diff: bool,
|
||||
auto_fix: bool,
|
||||
) -> None:
|
||||
"""Generate commit message suggestions for staged changes."""
|
||||
config: Config = ctx.get("config", get_config())
|
||||
|
||||
if conventional is None:
|
||||
conventional = config.conventional_by_default
|
||||
if interactive is None:
|
||||
interactive = config.interactive
|
||||
if show_diff is None:
|
||||
show_diff = config.show_diff
|
||||
|
||||
git_handler = get_git_handler()
|
||||
|
||||
if not git_handler.is_repository():
|
||||
click.echo(click.style("Error: Not in a git repository", fg="red"), err=True)
|
||||
click.echo("Please run this command from within a git repository.", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if not git_handler.is_staged():
|
||||
click.echo(click.style("No staged changes found.", fg="yellow"))
|
||||
click.echo("Please stage your changes first with 'git add <files>'", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
diff = git_handler.get_staged_changes()
|
||||
if show_diff:
|
||||
click.echo("\nStaged diff:")
|
||||
click.echo("-" * 50)
|
||||
click.echo(diff[:2000] + "..." if len(diff) > 2000 else diff)
|
||||
click.echo("-" * 50)
|
||||
|
||||
cache_manager = get_cache_manager(config)
|
||||
|
||||
cached = cache_manager.get(diff, conventional=conventional, model=model or config.ollama_model)
|
||||
if cached:
|
||||
messages = cached
|
||||
click.echo(click.style("Using cached suggestions", fg="cyan"))
|
||||
else:
|
||||
ollama_client = get_client(config)
|
||||
if model:
|
||||
ollama_client.model = model
|
||||
if base_url:
|
||||
ollama_client.base_url = base_url
|
||||
|
||||
if not ollama_client.is_available():
|
||||
click.echo(click.style("Error: Ollama server is not available", fg="red"), err=True)
|
||||
click.echo(f"Please ensure Ollama is running at {ollama_client.base_url}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if not ollama_client.check_model_exists():
|
||||
click.echo(click.style(f"Model '{ollama_client.model}' not found", fg="yellow"), err=True)
|
||||
if click.confirm("Would you like to pull this model?"):
|
||||
if ollama_client.pull_model():
|
||||
click.echo(click.style("Model pulled successfully", fg="green"))
|
||||
else:
|
||||
click.echo(click.style("Failed to pull model", fg="red"), err=True)
|
||||
sys.exit(1)
|
||||
else:
|
||||
available = ollama_client.list_models()
|
||||
if available:
|
||||
click.echo("Available models:", err=True)
|
||||
for m in available[:10]:
|
||||
click.echo(f" - {m.get('name', 'unknown')}", err=True)
|
||||
sys.exit(1)
|
||||
def cli():
|
||||
"""AI-powered Git commit message generator."""
|
||||
pass
|
||||
|
||||
@cli.command()
|
||||
@click.option('--conventional', is_flag=True, help='Generate conventional commit format')
|
||||
@click.option('--model', default=None, help='Ollama model to use')
|
||||
@click.option('--base-url', default=None, help='Ollama API base URL')
|
||||
def generate(conventional, model, base_url):
|
||||
"""Generate a commit message for staged changes."""
|
||||
try:
|
||||
commit_history = git_handler.get_commit_history(max_commits=3)
|
||||
context = "\n".join(f"- {c['hash']}: {c['message']}" for c in commit_history)
|
||||
config = load_config()
|
||||
model = model or config.get('model', 'qwen2.5-coder:3b')
|
||||
base_url = base_url or config.get('base_url', 'http://localhost:11434')
|
||||
|
||||
response = ollama_client.generate_commit_message(
|
||||
diff=diff, context=context if context else None, conventional=conventional, model=model
|
||||
)
|
||||
staged = get_staged_changes()
|
||||
if not staged:
|
||||
click.echo("No staged changes found. Stage your changes first.")
|
||||
return
|
||||
|
||||
messages = [m.strip() for m in response.split("\n") if m.strip() and not m.strip().lower().startswith("suggestion")]
|
||||
history = get_commit_history()
|
||||
prompt = build_prompt(staged, conventional=conventional, history=history)
|
||||
|
||||
if len(messages) == 1:
|
||||
single = messages[0].split("1.", "2.", "3.")
|
||||
if len(single) > 1:
|
||||
messages = [s.strip() for s in single if s.strip()]
|
||||
|
||||
messages = messages[:config.num_suggestions]
|
||||
|
||||
cache_manager.set(diff, messages, conventional=conventional, model=model or config.ollama_model)
|
||||
|
||||
except OllamaError as e:
|
||||
click.echo(click.style(f"Error generating commit message: {e}", fg="red"), err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if not messages:
|
||||
click.echo(click.style("No suggestions generated", fg="yellow"), err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if conventional and auto_fix:
|
||||
fixed_messages = []
|
||||
for msg in messages:
|
||||
is_valid, errors = validate_commit_message(msg)
|
||||
if not is_valid:
|
||||
fixed = ConventionalCommitFixer.fix(msg, diff)
|
||||
fixed_messages.append(fixed)
|
||||
else:
|
||||
fixed_messages.append(msg)
|
||||
messages = fixed_messages
|
||||
|
||||
click.echo("\n" + click.style("Suggested commit messages:", fg="green"))
|
||||
for i, msg in enumerate(messages, 1):
|
||||
click.echo(f" {i}. {msg}")
|
||||
message = generate_commit_message(prompt, model=model, base_url=base_url)
|
||||
|
||||
if conventional:
|
||||
click.echo()
|
||||
for i, msg in enumerate(messages, 1):
|
||||
is_valid, errors = validate_commit_message(msg)
|
||||
if is_valid:
|
||||
click.echo(click.style(f" {i}. [Valid conventional format]", fg="green"))
|
||||
else:
|
||||
click.echo(click.style(f" {i}. [Format issues: {', '.join(errors)}]", fg="yellow"))
|
||||
is_valid, suggestion = validate_conventional(message)
|
||||
if not is_valid:
|
||||
fixed = fix_conventional(message, staged)
|
||||
if fixed:
|
||||
message = fixed
|
||||
|
||||
if interactive:
|
||||
choice = click.prompt("\nSelect a message (number) or press Enter to see all:", type=int, default=0, show_default=False)
|
||||
if 1 <= choice <= len(messages):
|
||||
selected = messages[choice - 1]
|
||||
click.echo(f"\nSelected: {selected}")
|
||||
click.echo(f"\nTo commit, run:")
|
||||
click.echo(f' git commit -m "{selected}"')
|
||||
else:
|
||||
click.echo(f"\nTo use the first suggestion, run:")
|
||||
click.echo(click.style(f' git commit -m "{messages[0]}"', fg="cyan"))
|
||||
click.echo(f"\nSuggested commit message:\n{message}")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option("--model", help="Ollama model to check")
|
||||
@click.pass_obj
|
||||
def status(ctx: dict, model: str | None) -> None:
|
||||
"""Check Ollama and repository status."""
|
||||
config: Config = ctx.get("config", get_config())
|
||||
|
||||
click.echo("Git Commit AI Status")
|
||||
click.echo("=" * 40)
|
||||
|
||||
git_handler = get_git_handler()
|
||||
click.echo(f"\nGit Repository: {'Yes' if git_handler.is_repository() else 'No'}")
|
||||
|
||||
if git_handler.is_repository():
|
||||
click.echo(f"Staged Changes: {'Yes' if git_handler.is_staged() else 'No'}")
|
||||
|
||||
ollama_client = get_client(config)
|
||||
if model:
|
||||
ollama_client.model = model
|
||||
|
||||
click.echo(f"\nOllama:")
|
||||
click.echo(f" Base URL: {ollama_client.base_url}")
|
||||
click.echo(f" Model: {ollama_client.model}")
|
||||
|
||||
if ollama_client.is_available():
|
||||
click.echo(f" Status: {click.style('Running', fg='green')}")
|
||||
|
||||
if ollama_client.check_model_exists():
|
||||
click.echo(f" Model: {click.style('Available', fg='green')}")
|
||||
else:
|
||||
click.echo(f" Model: {click.style('Not found', fg='yellow')}")
|
||||
available = ollama_client.list_models()
|
||||
if available:
|
||||
click.echo(" Available models:")
|
||||
for m in available[:5]:
|
||||
click.echo(f" - {m.get('name', 'unknown')}")
|
||||
else:
|
||||
click.echo(f" Status: {click.style('Not running', fg='red')}")
|
||||
click.echo(f" Start Ollama with: {click.style('ollama serve', fg='cyan')}")
|
||||
|
||||
cache_manager = get_cache_manager(config)
|
||||
stats = cache_manager.get_stats()
|
||||
click.echo(f"\nCache:")
|
||||
click.echo(f" Enabled: {'Yes' if stats['enabled'] else 'No'}")
|
||||
click.echo(f" Entries: {stats['entries']}")
|
||||
if stats['entries'] > 0:
|
||||
click.echo(f" Size: {stats['size_bytes'] // 1024} KB")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.pass_obj
|
||||
def models(ctx: dict) -> None:
|
||||
"""List available Ollama models."""
|
||||
config: Config = ctx.get("config", get_config())
|
||||
ollama_client = get_client(config)
|
||||
|
||||
if not ollama_client.is_available():
|
||||
click.echo(click.style("Error: Ollama server is not available", fg="red"), err=True)
|
||||
sys.exit(1)
|
||||
|
||||
models = ollama_client.list_models()
|
||||
if models:
|
||||
click.echo("Available models:")
|
||||
for m in models:
|
||||
name = m.get("name", "unknown")
|
||||
size = m.get("size", 0)
|
||||
size_mb = size / (1024 * 1024) if size else 0
|
||||
click.echo(f" {name} ({size_mb:.1f} MB)")
|
||||
else:
|
||||
click.echo("No models found.")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option("--model", help="Model to pull")
|
||||
@click.pass_obj
|
||||
def pull(ctx: dict, model: str | None) -> None:
|
||||
"""Pull an Ollama model."""
|
||||
config: Config = ctx.get("config", get_config())
|
||||
ollama_client = get_client(config)
|
||||
|
||||
model = model or config.ollama_model
|
||||
|
||||
if not ollama_client.is_available():
|
||||
click.echo(click.style("Error: Ollama server is not available", fg="red"), err=True)
|
||||
sys.exit(1)
|
||||
|
||||
with click.progressbar(length=100, label=f"Pulling {model}", show_percent=True, show_pos=True) as progress:
|
||||
success = ollama_client.pull_model(model)
|
||||
if success:
|
||||
progress.update(100)
|
||||
click.echo(click.style(f"\nModel {model} pulled successfully", fg="green"))
|
||||
else:
|
||||
click.echo(click.style(f"\nFailed to pull model {model}", fg="red"), err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option("--force", is_flag=True, help="Force cleanup without confirmation")
|
||||
@click.pass_obj
|
||||
def cache(ctx: dict, force: bool) -> None:
|
||||
"""Manage cache."""
|
||||
config: Config = ctx.get("config", get_config())
|
||||
cache_manager = get_cache_manager(config)
|
||||
|
||||
stats = cache_manager.get_stats()
|
||||
|
||||
click.echo("Cache Status:")
|
||||
click.echo(f" Enabled: {'Yes' if stats['enabled'] else 'No'}")
|
||||
click.echo(f" Entries: {stats['entries']}")
|
||||
click.echo(f" Expired: {stats['expired']}")
|
||||
click.echo(f" Size: {stats['size_bytes'] // 1024} KB")
|
||||
|
||||
if stats['entries'] > 0:
|
||||
if force or click.confirm("\nClear all cache entries?"):
|
||||
cleared = cache_manager.clear()
|
||||
click.echo(f"Cleared {cleared} entries")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("message")
|
||||
@click.option("--auto-fix", is_flag=True, help="Attempt to auto-fix format issues")
|
||||
def validate(message: str, auto_fix: bool) -> None:
|
||||
"""Validate a commit message format."""
|
||||
is_valid, errors = validate_commit_message(message)
|
||||
|
||||
if is_valid:
|
||||
click.echo(click.style("Valid commit message", fg="green"))
|
||||
else:
|
||||
click.echo(click.style("Invalid commit message:", fg="red"))
|
||||
for error in errors:
|
||||
click.echo(f" - {error}")
|
||||
|
||||
if auto_fix:
|
||||
fixed = ConventionalCommitFixer.fix(message, "")
|
||||
if fixed != message:
|
||||
click.echo()
|
||||
click.echo(click.style(f"Suggested fix: {fixed}", fg="cyan"))
|
||||
|
||||
sys.exit(0 if is_valid else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
except Exception as e:
|
||||
click.echo(f"Error: {e}")
|
||||
|
||||
Reference in New Issue
Block a user