🔐 Update: versioning.py um GPG-Tagging erweitert

This commit is contained in:
Oliver 2025-07-09 19:02:52 +02:00
parent 91baacd355
commit 2a9fa1e548
No known key found for this signature in database
GPG key ID: 770C356E927D1E0E

View file

@ -1,5 +1,3 @@
# versioning.py
import re import re
import subprocess import subprocess
from pathlib import Path from pathlib import Path
@ -12,13 +10,11 @@ CHANGELOG_FILE = Path("CHANGELOG.md")
VERSION_FILE = Path("__version__.py") VERSION_FILE = Path("__version__.py")
VERSION_PATTERN = r"## \[v?(\d+\.\d+\.\d+)\]" VERSION_PATTERN = r"## \[v?(\d+\.\d+\.\d+)\]"
def get_latest_version(): def get_latest_version():
content = CHANGELOG_FILE.read_text(encoding="utf-8") content = CHANGELOG_FILE.read_text(encoding="utf-8")
matches = re.findall(VERSION_PATTERN, content) matches = re.findall(VERSION_PATTERN, content)
return matches[0] if matches else "0.0.0" return matches[0] if matches else "0.0.0"
def bump_version(version: str, level: str = "patch") -> str: def bump_version(version: str, level: str = "patch") -> str:
major, minor, patch = map(int, version.split(".")) major, minor, patch = map(int, version.split("."))
if level == "major": if level == "major":
@ -27,132 +23,40 @@ def bump_version(version: str, level: str = "patch") -> str:
return f"{major}.{minor + 1}.0" return f"{major}.{minor + 1}.0"
return f"{major}.{minor}.{patch + 1}" return f"{major}.{minor}.{patch + 1}"
def write_version_file(version: str): def write_version_file(version: str):
VERSION_FILE.write_text(f"VERSION = \"{version}\"\n", encoding="utf-8") VERSION_FILE.write_text(f"VERSION = \"{version}\"\n", encoding="utf-8")
typer.echo(f"🔢 __version__.py aktualisiert auf {version}")
def update_changelog(version: str):
def prepend_changelog(version: str): date = datetime.now().strftime("%Y-%m-%d")
today = datetime.today().strftime("%Y-%m-%d") new_entry = f"## [{version}] - {date}\n\n- Beschreibung...\n\n"
new_entry = f"\n\n## [v{version}] {today}\n\n### 💡 Neue Funktionen\n- \n\n### 🔧 Änderungen & Fixes\n- \n\n### 📦 Internes\n- "
original = CHANGELOG_FILE.read_text(encoding="utf-8")
CHANGELOG_FILE.write_text(new_entry + original, encoding="utf-8")
typer.echo(f"📝 Neuer Eintrag für v{version} zu CHANGELOG.md hinzugefügt")
def validate_changelog(version: str) -> bool:
content = CHANGELOG_FILE.read_text(encoding="utf-8") content = CHANGELOG_FILE.read_text(encoding="utf-8")
pattern = rf"## \[v?{re.escape(version)}\](.*?)^## \[" CHANGELOG_FILE.write_text(new_entry + content, encoding="utf-8")
match = re.search(pattern, content + "\n## [", re.DOTALL | re.MULTILINE)
if match:
section = match.group(1).strip()
if any(line.strip() != "-" for line in section.splitlines() if line.strip()):
return True
typer.echo("⚠️ CHANGELOG-Eintrag ist noch leer oder unvollständig.")
return False
def create_git_tag(version: str, signed: bool = True):
def tag_exists(tag: str) -> bool: tag_args = ["git", "tag"]
result = subprocess.run(["git", "tag"], capture_output=True, text=True) if signed:
return tag in result.stdout.splitlines() tag_args.append("-s") # signierter Tag
def ensure_branch(expected_branch="main"):
result = subprocess.run(["git", "rev-parse", "--abbrev-ref", "HEAD"], capture_output=True, text=True)
branch = result.stdout.strip()
if branch != expected_branch:
typer.echo(f"⛔ Du befindest dich auf Branch '{branch}', erwartet wird '{expected_branch}'")
raise typer.Exit(code=1)
def extract_changelog_entry(version: str) -> str:
content = CHANGELOG_FILE.read_text(encoding="utf-8")
pattern = rf"## \[v?{re.escape(version)}\](.*?)^## \["
match = re.search(pattern, content + "\n## [", re.DOTALL | re.MULTILINE)
return match.group(1).strip() if match else ""
def create_git_tag(version: str):
try:
if tag_exists(f"v{version}"):
typer.echo(f"⚠️ Tag v{version} existiert bereits.")
return
subprocess.run(["git", "add", "."], check=True)
subprocess.run(["git", "commit", "-m", f"🔖 Release v{version}"], check=True)
subprocess.run(["git", "tag", f"v{version}"], check=True)
typer.echo(f"🏷️ Git-Tag 'v{version}' erstellt und commit durchgeführt.")
except subprocess.CalledProcessError:
typer.echo("⚠️ Git-Fehler beim Taggen oder Committen. Bitte manuell prüfen.")
def push_to_github():
try:
subprocess.run(["git", "push"], check=True)
subprocess.run(["git", "push", "--tags"], check=True)
typer.echo("🚀 Änderungen und Tags an GitHub gepusht.")
except subprocess.CalledProcessError:
typer.echo("⚠️ Fehler beim Pushen zu GitHub. Bitte Zugang oder Netzwerk prüfen.")
@app.command()
def list():
"Listet alle verfügbaren Versionen aus dem CHANGELOG"
typer.echo("\n📚 Verfügbare Versionen im CHANGELOG:")
content = CHANGELOG_FILE.read_text(encoding="utf-8")
versions = re.findall(VERSION_PATTERN, content)
for v in versions:
typer.echo(f"- v{v}")
@app.command()
def rollback():
"Letzte Version zurückrollen (Tag löschen + Commit zurücknehmen)"
last_version = get_latest_version()
if typer.confirm(f"⚠️ Letzte Version 'v{last_version}' wirklich zurücknehmen?"):
try:
subprocess.run(["git", "tag", "-d", f"v{last_version}"], check=True)
subprocess.run(["git", "reset", "--hard", "HEAD~1"], check=True)
typer.echo(f"🔙 Version 'v{last_version}' wurde zurückgerollt.")
except subprocess.CalledProcessError:
typer.echo("❌ Rollback fehlgeschlagen.")
else: else:
typer.echo("⛔ Abgebrochen.") tag_args.append("-a") # un-signierter, annotierter Tag
tag_args += [f"v{version}", "-m", f"Release v{version}"]
subprocess.run(tag_args, check=True)
def push_git_tag(version: str):
subprocess.run(["git", "push"], check=True)
subprocess.run(["git", "push", "origin", f"v{version}"], check=True)
@app.command() @app.command()
def create(level: str = typer.Option("patch", help="Versionstyp: patch, minor oder major"), def create(level: str = "patch", push: bool = False, unsigned: bool = False):
push: bool = typer.Option(False, help="Änderungen direkt an GitHub pushen")): current = get_latest_version()
"Neue Version erstellen inkl. CHANGELOG, Git-Tag und optional Push" new_version = bump_version(current, level)
ensure_branch() write_version_file(new_version)
update_changelog(new_version)
current_version = get_latest_version() subprocess.run(["git", "add", "."], check=True)
next_version = bump_version(current_version, level) subprocess.run(["git", "commit", "-m", f"Bump version to v{new_version}"], check=True)
create_git_tag(new_version, signed=not unsigned)
typer.echo(f"💡 Aktuelle Version: {current_version}") if push:
typer.echo(f"🚀 Neue Version: {next_version}") push_git_tag(new_version)
typer.echo(f"✅ Version {new_version} erstellt und getaggt{' (unsigned)' if unsigned else ' (signed)'}.")
if typer.confirm("Version übernehmen und eintragen?"):
write_version_file(next_version)
prepend_changelog(next_version)
typer.echo("\nBitte CHANGELOG.md bearbeiten und danach fortfahren.")
typer.prompt("Drücke Enter, sobald du den neuen Abschnitt ausgefüllt hast")
if not validate_changelog(next_version):
typer.echo("❌ Release abgebrochen: Bitte fülle den CHANGELOG-Eintrag aus.")
raise typer.Exit(code=1)
create_git_tag(next_version)
if push:
push_to_github()
typer.echo(f"✅ Version {next_version} erfolgreich erstellt.")
else:
typer.echo("❌ Abgebrochen.")
if __name__ == "__main__": if __name__ == "__main__":
app() app()