- Writing custom git flow scripts - a start

This commit is contained in:
Josako
2025-12-11 09:27:21 +01:00
parent 0f8bda0aef
commit fe9fc047ff
14 changed files with 720 additions and 0 deletions

View File

View File

@@ -0,0 +1,34 @@
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class GitFlowConfig:
main_branch: str = "main"
develop_branch: str = "develop"
remote_name: str = "origin"
feature_prefix: str = "feature/"
bugfix_prefix: str = "bugfix/"
hotfix_prefix: str = "hotfix/"
release_prefix: str = "release/"
tag_format: str = "v{version}"
# Merge-strategie kan later per actie configureerbaar worden
use_no_ff_for_feature: bool = True
use_no_ff_for_release: bool = True
use_no_ff_for_hotfix: bool = True
CONFIG = GitFlowConfig()
def load_config() -> None:
"""Laad configuratie.
Voor nu gebruiken we enkel harde defaults. Later kunnen we hier
een bestand (bv. yaml/toml) inlezen en `CONFIG` overschrijven.
"""
# TODO: optioneel configuratiebestand ondersteunen.
return None

View File

@@ -0,0 +1,94 @@
from __future__ import annotations
import subprocess
from typing import Iterable
from . import output
class GitError(RuntimeError):
pass
def _run_git(args: Iterable[str], *, capture_output: bool = False, check: bool = True) -> subprocess.CompletedProcess:
cmd = ["git", *args]
result = subprocess.run(
cmd,
text=True,
capture_output=capture_output,
check=False,
)
if check and result.returncode != 0:
stderr = result.stderr.strip() if result.stderr else ""
raise GitError(f"git {' '.join(args)} failed ({result.returncode}): {stderr}")
return result
def get_current_branch() -> str:
result = _run_git(["rev-parse", "--abbrev-ref", "HEAD"], capture_output=True)
return (result.stdout or "").strip()
def is_clean_working_tree() -> bool:
result = _run_git(["status", "--porcelain"], capture_output=True)
return (result.stdout or "").strip() == ""
def ensure_clean_working_tree() -> None:
if not is_clean_working_tree():
raise GitError(
"Working tree is niet clean. Commit of stash je wijzigingen voor je deze actie uitvoert."
)
def ensure_branch_exists(branch: str) -> None:
try:
_run_git(["show-ref", "--verify", f"refs/heads/{branch}"], capture_output=True)
except GitError as exc:
raise GitError(f"Branch '{branch}' bestaat niet lokaal.") from exc
def checkout_branch(branch: str) -> None:
_run_git(["checkout", branch])
def create_branch(branch: str, base: str) -> None:
_run_git(["checkout", base])
_run_git(["checkout", "-b", branch])
def fetch_remote(remote: str) -> None:
_run_git(["fetch", remote])
def ensure_not_behind_remote(branch: str, remote: str) -> None:
"""Controleer of branch niet achterloopt op remote.
We gebruiken `git rev-list --left-right --count local...remote` om
ahead/behind te bepalen.
"""
remote_ref = f"{remote}/{branch}"
try:
result = _run_git(
["rev-list", "--left-right", "--count", f"{branch}...{remote_ref}"],
capture_output=True,
)
except GitError:
# Geen tracking remote; in die gevallen doen we geen check.
return
output_str = (result.stdout or "").strip()
if not output_str:
return
ahead_str, behind_str = output_str.split()
ahead = int(ahead_str)
behind = int(behind_str)
if behind > 0:
raise GitError(
f"Branch '{branch}' loopt {behind} commit(s) achter op {remote_ref}. "
f"Doe eerst een 'git pull --ff-only' of werk te wijzigingen lokaal bij."
)

View File

@@ -0,0 +1,61 @@
from __future__ import annotations
import sys
class _Colors:
RESET = "\033[0m"
BOLD = "\033[1m"
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
def _print(message: str, *, prefix: str = "", color: str | None = None, stream=None) -> None:
if stream is None:
stream = sys.stdout
text = f"{prefix} {message}" if prefix else message
if color:
text = f"{color}{text}{_Colors.RESET}"
print(text, file=stream)
def info(message: str) -> None:
_print(message, prefix="", color=_Colors.BLUE)
def success(message: str) -> None:
_print(message, prefix="", color=_Colors.GREEN)
def warning(message: str) -> None:
_print(message, prefix="⚠️", color=_Colors.YELLOW, stream=sys.stderr)
def error(message: str) -> None:
_print(message, prefix="", color=_Colors.RED, stream=sys.stderr)
def heading(message: str) -> None:
_print(message, prefix="", color=_Colors.BOLD)
def plain(message: str) -> None:
_print(message)
class Notifier:
"""Abstractielaag voor toekomstige auditieve output.
Voor nu enkel console-notificaties; later kan dit uitgebreid
worden met TTS, systeemmeldingen, ...
"""
@staticmethod
def notify_event(event: str, detail: str | None = None) -> None: # pragma: no cover - placeholder
if detail:
info(f"[{event}] {detail}")
else:
info(f"[{event}]")