Add CLI modules: validate, sync, merge, history
This commit is contained in:
156
confsync/cli/history.py
Normal file
156
confsync/cli/history.py
Normal 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]")
|
||||||
Reference in New Issue
Block a user