Add CLI modules: validate, sync, merge, history
Some checks failed
CI / test (push) Failing after 6s
CI / build (push) Has been skipped

This commit is contained in:
2026-02-04 20:05:33 +00:00
parent 77e5afe0d0
commit 2f7e90148f

156
confsync/cli/history.py Normal file
View File

@@ -0,0 +1,156 @@
"""History command for tracking configuration changes."""
from typing import Dict, Optional
import typer
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from datetime import datetime
from confsync.utils.git_utils import GitManager
history_cmd = typer.Typer(
name="history",
help="View and manage configuration change history",
no_args_is_help=True,
)
console = Console()
@history_cmd.command("list")
def history_list(
limit: int = typer.Option(
20, "--limit", "-l",
help="Maximum number of entries to show"
),
repo: Optional[str] = typer.Option(
None, "--repo", "-r",
help="Git repository to read history from"
),
manifest: str = typer.Option(
"confsync_manifest.yaml",
"--manifest", "-m",
help="Path to manifest file"
),
):
"""List configuration change history."""
git = GitManager(repo_path=repo)
commits = git.get_commit_history(manifest, max_count=limit)
if not commits:
console.print("[yellow]No history found.[/yellow]")
return
table = Table(title="Configuration History")
table.add_column("Commit", style="cyan", no_wrap=True)
table.add_column("Date", style="magenta")
table.add_column("Author", style="green")
table.add_column("Message", style="white")
for commit in commits:
date = datetime.fromisoformat(commit["authored_datetime"]).strftime("%Y-%m-%d %H:%M")
message = commit["message"][:60] + "..." if len(commit["message"]) > 60 else commit["message"]
table.add_row(
commit["hexsha"][:7],
date,
commit["author"].split("<")[0].strip(),
message,
)
console.print(table)
@history_cmd.command("show")
def history_show(
commit: str = typer.Argument(
...,
help="Commit hash to show"
),
repo: Optional[str] = typer.Option(
None, "--repo", "-r",
help="Git repository"
),
file: Optional[str] = typer.Option(
None, "--file", "-f",
help="Show only specific file from commit"
),
):
"""Show details of a specific commit."""
git = GitManager(repo_path=repo)
commits = git.get_commit_history(file, max_count=100)
target = None
for c in commits:
if c["hexsha"].startswith(commit):
target = c
break
if not target:
console.print(f"[red]Error:[/red] Commit not found: {commit}")
return
diff = git.get_file_diff(file or "")
console.print(Panel.fit(
f"[bold]Commit {target['hexsha'][:7]}[/bold]",
style="blue",
subtitle=target["authored_datetime"],
))
console.print(f"\n[bold]Author:[/bold] {target['author']}")
console.print("\n[bold]Message:[/bold]")
console.print(target['message'])
if diff:
console.print("\n[bold]Changes:[/bold]")
console.print(diff)
@history_cmd.command("rollback")
def history_rollback(
commit: str = typer.Argument(
...,
help="Commit hash to rollback to"
),
repo: Optional[str] = typer.Option(
None, "--repo", "-r",
help="Git repository"
),
file: Optional[str] = typer.Option(
None, "--file", "-f",
help="Rollback specific file only"
),
dry_run: bool = typer.Option(
False, "--dry-run", "-n",
help="Preview without making changes"
),
):
"""Rollback configurations to a previous commit."""
git = GitManager(repo_path=repo)
if dry_run:
console.print(f"[bold]Dry Run - Would rollback to {commit[:7]}[/bold]")
if file:
console.print(f"File: {file}")
commits = git.get_commit_history(file, max_count=5)
for c in commits:
if c["hexsha"].startswith(commit):
console.print(f"Target: {c['message']}")
break
console.print("[yellow]No changes made[/yellow]")
return
if file:
console.print(f"[bold]Rolling back {file} to {commit[:7]}[/bold]")
else:
console.print(f"[bold]Rolling back to commit {commit[:7]}[/bold]")
if git.checkout(commit):
console.print(Panel.fit(
"[bold green]Rollback Successful![/bold green]",
style="green",
))
else:
console.print("[red]Error: Rollback failed[/red]")