- Writing custom git flow scripts - finishing up without extensive testing
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from ..core import config, git_api, output
|
from ..core import config, git_api, hooks, output
|
||||||
|
|
||||||
|
|
||||||
def _bugfix_branch_name(name: str) -> str:
|
def _bugfix_branch_name(name: str) -> str:
|
||||||
@@ -20,6 +20,15 @@ def handle_bugfix_start(args) -> int:
|
|||||||
output.info(f"Aanmaken van bugfix branch '{branch_name}' vanaf '{cfg.develop_branch}'")
|
output.info(f"Aanmaken van bugfix branch '{branch_name}' vanaf '{cfg.develop_branch}'")
|
||||||
git_api.create_branch(branch_name, cfg.develop_branch)
|
git_api.create_branch(branch_name, cfg.develop_branch)
|
||||||
output.success(f"Bugfix branch '{branch_name}' is aangemaakt en gecheckt out.")
|
output.success(f"Bugfix branch '{branch_name}' is aangemaakt en gecheckt out.")
|
||||||
|
|
||||||
|
# Hooks na succesvol aanmaken van een bugfix branch
|
||||||
|
hooks.run_hooks(
|
||||||
|
"bugfix_start",
|
||||||
|
{
|
||||||
|
"branch": branch_name,
|
||||||
|
"base_branch": cfg.develop_branch,
|
||||||
|
},
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
except git_api.GitError as exc:
|
except git_api.GitError as exc:
|
||||||
output.error(str(exc))
|
output.error(str(exc))
|
||||||
@@ -36,11 +45,24 @@ def handle_bugfix_finish(args) -> int:
|
|||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
current = git_api.get_current_branch()
|
current = git_api.get_current_branch()
|
||||||
if not current.startswith(cfg.bugfix_prefix):
|
if current.startswith(cfg.bugfix_prefix):
|
||||||
|
bugfix_branch = current
|
||||||
|
else:
|
||||||
|
branches = git_api.list_local_branches_with_prefix(cfg.bugfix_prefix)
|
||||||
|
if not branches:
|
||||||
|
raise git_api.GitError(
|
||||||
|
"Er zijn geen lokale bugfix branches gevonden. "
|
||||||
|
"Maak eerst een bugfix branch aan of geef een naam op."
|
||||||
|
)
|
||||||
|
|
||||||
|
output.heading("Beschikbare bugfix branches")
|
||||||
|
for b in branches:
|
||||||
|
output.plain(f"- {b}")
|
||||||
|
|
||||||
raise git_api.GitError(
|
raise git_api.GitError(
|
||||||
"Je zit niet op een bugfix branch. Geef de bugfix-naam expliciet door (zonder prefix)."
|
"Je zit niet op een bugfix branch. Kies een van de bovenstaande namen "
|
||||||
|
"en voer het commando opnieuw uit, bv.: gitflow bugfix finish <naam-zonder-prefix>."
|
||||||
)
|
)
|
||||||
bugfix_branch = current
|
|
||||||
else:
|
else:
|
||||||
bugfix_branch = _bugfix_branch_name(name)
|
bugfix_branch = _bugfix_branch_name(name)
|
||||||
|
|
||||||
@@ -63,6 +85,23 @@ def handle_bugfix_finish(args) -> int:
|
|||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
output.success(f"Bugfix branch '{bugfix_branch}' is gemerged naar '{cfg.develop_branch}'.")
|
output.success(f"Bugfix branch '{bugfix_branch}' is gemerged naar '{cfg.develop_branch}'.")
|
||||||
|
|
||||||
|
# Optionele cleanup
|
||||||
|
if cfg.delete_bugfix_after_finish:
|
||||||
|
output.info(f"Opruimen van lokale bugfix branch '{bugfix_branch}'")
|
||||||
|
try:
|
||||||
|
git_api._run_git(["branch", "-d", bugfix_branch]) # type: ignore[attr-defined]
|
||||||
|
except git_api.GitError as exc:
|
||||||
|
output.warning(f"Kon bugfix branch '{bugfix_branch}' niet verwijderen: {exc}")
|
||||||
|
|
||||||
|
# Hooks na succesvolle bugfix-finish
|
||||||
|
hooks.run_hooks(
|
||||||
|
"bugfix_finish",
|
||||||
|
{
|
||||||
|
"branch": bugfix_branch,
|
||||||
|
"base_branch": cfg.develop_branch,
|
||||||
|
},
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
except git_api.GitError as exc:
|
except git_api.GitError as exc:
|
||||||
output.error(str(exc))
|
output.error(str(exc))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from ..core import config, git_api, output
|
from ..core import config, git_api, hooks, output
|
||||||
|
|
||||||
|
|
||||||
def _feature_branch_name(name: str) -> str:
|
def _feature_branch_name(name: str) -> str:
|
||||||
@@ -20,6 +20,15 @@ def handle_feature_start(args) -> int:
|
|||||||
output.info(f"Aanmaken van feature branch '{branch_name}' vanaf '{cfg.develop_branch}'")
|
output.info(f"Aanmaken van feature branch '{branch_name}' vanaf '{cfg.develop_branch}'")
|
||||||
git_api.create_branch(branch_name, cfg.develop_branch)
|
git_api.create_branch(branch_name, cfg.develop_branch)
|
||||||
output.success(f"Feature branch '{branch_name}' is aangemaakt en gecheckt out.")
|
output.success(f"Feature branch '{branch_name}' is aangemaakt en gecheckt out.")
|
||||||
|
|
||||||
|
# Hooks na succesvol aanmaken van een feature branch
|
||||||
|
hooks.run_hooks(
|
||||||
|
"feature_start",
|
||||||
|
{
|
||||||
|
"branch": branch_name,
|
||||||
|
"base_branch": cfg.develop_branch,
|
||||||
|
},
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
except git_api.GitError as exc:
|
except git_api.GitError as exc:
|
||||||
output.error(str(exc))
|
output.error(str(exc))
|
||||||
@@ -36,11 +45,26 @@ def handle_feature_finish(args) -> int:
|
|||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
current = git_api.get_current_branch()
|
current = git_api.get_current_branch()
|
||||||
if not current.startswith(cfg.feature_prefix):
|
if current.startswith(cfg.feature_prefix):
|
||||||
|
feature_branch = current
|
||||||
|
else:
|
||||||
|
# Geen naam en we zitten niet op een feature-branch: toon een lijst
|
||||||
|
# met beschikbare feature-branches om de gebruiker te helpen kiezen.
|
||||||
|
branches = git_api.list_local_branches_with_prefix(cfg.feature_prefix)
|
||||||
|
if not branches:
|
||||||
|
raise git_api.GitError(
|
||||||
|
"Er zijn geen lokale feature branches gevonden. "
|
||||||
|
"Maak eerst een feature branch aan of geef een naam op."
|
||||||
|
)
|
||||||
|
|
||||||
|
output.heading("Beschikbare feature branches")
|
||||||
|
for b in branches:
|
||||||
|
output.plain(f"- {b}")
|
||||||
|
|
||||||
raise git_api.GitError(
|
raise git_api.GitError(
|
||||||
"Je zit niet op een feature branch. Geef de feature-naam expliciet door (zonder prefix)."
|
"Je zit niet op een feature branch. Kies een van de bovenstaande namen "
|
||||||
|
"en voer het commando opnieuw uit, bv.: gitflow feature finish <naam-zonder-prefix>."
|
||||||
)
|
)
|
||||||
feature_branch = current
|
|
||||||
else:
|
else:
|
||||||
feature_branch = _feature_branch_name(name)
|
feature_branch = _feature_branch_name(name)
|
||||||
|
|
||||||
@@ -64,6 +88,23 @@ def handle_feature_finish(args) -> int:
|
|||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
output.success(f"Feature branch '{feature_branch}' is gemerged naar '{cfg.develop_branch}'.")
|
output.success(f"Feature branch '{feature_branch}' is gemerged naar '{cfg.develop_branch}'.")
|
||||||
|
|
||||||
|
# Optionele cleanup van de feature branch
|
||||||
|
if cfg.delete_feature_after_finish:
|
||||||
|
output.info(f"Opruimen van lokale feature branch '{feature_branch}'")
|
||||||
|
try:
|
||||||
|
git_api._run_git(["branch", "-d", feature_branch]) # type: ignore[attr-defined]
|
||||||
|
except git_api.GitError as exc:
|
||||||
|
output.warning(f"Kon feature branch '{feature_branch}' niet verwijderen: {exc}")
|
||||||
|
|
||||||
|
# Hooks na succesvolle feature-finish
|
||||||
|
hooks.run_hooks(
|
||||||
|
"feature_finish",
|
||||||
|
{
|
||||||
|
"branch": feature_branch,
|
||||||
|
"base_branch": cfg.develop_branch,
|
||||||
|
},
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
except git_api.GitError as exc:
|
except git_api.GitError as exc:
|
||||||
output.error(str(exc))
|
output.error(str(exc))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from ..core import config, git_api, output
|
from ..core import config, git_api, hooks, output
|
||||||
|
|
||||||
|
|
||||||
def _hotfix_branch_name(name: str) -> str:
|
def _hotfix_branch_name(name: str) -> str:
|
||||||
@@ -32,6 +32,16 @@ def handle_hotfix_start(args) -> int:
|
|||||||
output.info(f"Aanmaken van hotfix branch '{branch_name}' vanaf '{cfg.main_branch}'")
|
output.info(f"Aanmaken van hotfix branch '{branch_name}' vanaf '{cfg.main_branch}'")
|
||||||
git_api.create_branch(branch_name, cfg.main_branch)
|
git_api.create_branch(branch_name, cfg.main_branch)
|
||||||
output.success(f"Hotfix branch '{branch_name}' is aangemaakt en gecheckt out.")
|
output.success(f"Hotfix branch '{branch_name}' is aangemaakt en gecheckt out.")
|
||||||
|
|
||||||
|
# Hooks na succesvol aanmaken van een hotfix branch
|
||||||
|
hooks.run_hooks(
|
||||||
|
"hotfix_start",
|
||||||
|
{
|
||||||
|
"branch": branch_name,
|
||||||
|
"base_branch": cfg.main_branch,
|
||||||
|
"version": name,
|
||||||
|
},
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
except git_api.GitError as exc:
|
except git_api.GitError as exc:
|
||||||
output.error(str(exc))
|
output.error(str(exc))
|
||||||
@@ -42,6 +52,7 @@ def handle_hotfix_finish(args) -> int:
|
|||||||
cfg = config.CONFIG
|
cfg = config.CONFIG
|
||||||
try:
|
try:
|
||||||
name_arg = getattr(args, "name", None)
|
name_arg = getattr(args, "name", None)
|
||||||
|
env_arg = getattr(args, "env", None)
|
||||||
|
|
||||||
git_api.ensure_clean_working_tree()
|
git_api.ensure_clean_working_tree()
|
||||||
git_api.fetch_remote(cfg.remote_name)
|
git_api.fetch_remote(cfg.remote_name)
|
||||||
@@ -56,17 +67,32 @@ def handle_hotfix_finish(args) -> int:
|
|||||||
name = current[len(prefix) :]
|
name = current[len(prefix) :]
|
||||||
hotfix_branch = current
|
hotfix_branch = current
|
||||||
else:
|
else:
|
||||||
name = _ensure_version(None)
|
# Toon lijst van beschikbare hotfix branches
|
||||||
hotfix_branch = _hotfix_branch_name(name)
|
branches = git_api.list_local_branches_with_prefix(cfg.hotfix_prefix)
|
||||||
|
if not branches:
|
||||||
|
raise git_api.GitError(
|
||||||
|
"Er zijn geen lokale hotfix branches gevonden. "
|
||||||
|
"Maak eerst een hotfix branch aan of geef een naam op."
|
||||||
|
)
|
||||||
|
|
||||||
|
output.heading("Beschikbare hotfix branches")
|
||||||
|
for b in branches:
|
||||||
|
output.plain(f"- {b}")
|
||||||
|
|
||||||
|
raise git_api.GitError(
|
||||||
|
"Je zit niet op een hotfix branch. Kies een van de bovenstaande namen "
|
||||||
|
"en voer het commando opnieuw uit, bv.: gitflow hotfix finish <naam-of-versie>."
|
||||||
|
)
|
||||||
|
|
||||||
git_api.ensure_not_behind_remote(hotfix_branch, cfg.remote_name)
|
git_api.ensure_not_behind_remote(hotfix_branch, cfg.remote_name)
|
||||||
git_api.ensure_not_behind_remote(cfg.main_branch, cfg.remote_name)
|
git_api.ensure_not_behind_remote(cfg.main_branch, cfg.remote_name)
|
||||||
git_api.ensure_not_behind_remote(cfg.develop_branch, cfg.remote_name)
|
git_api.ensure_not_behind_remote(cfg.develop_branch, cfg.remote_name)
|
||||||
|
|
||||||
# Voor nu vragen we ook hier de versie/naam en gebruiken die voor de tag.
|
# Bepaal omgeving en tagnaam
|
||||||
# In een latere iteratie kunnen we hier automatische patch-bumping doen.
|
env = env_arg or cfg.hotfix_default_env
|
||||||
version_for_tag = name
|
suffix = cfg.environments.get(env, "")
|
||||||
tag_name = cfg.tag_format.format(version=version_for_tag)
|
base_tag = cfg.tag_format.format(version=name)
|
||||||
|
tag_name = f"{base_tag}{suffix}"
|
||||||
|
|
||||||
# Merge naar main
|
# Merge naar main
|
||||||
output.info(f"Mergen van '{hotfix_branch}' naar '{cfg.main_branch}' en tag '{tag_name}' aanmaken")
|
output.info(f"Mergen van '{hotfix_branch}' naar '{cfg.main_branch}' en tag '{tag_name}' aanmaken")
|
||||||
@@ -101,6 +127,26 @@ def handle_hotfix_finish(args) -> int:
|
|||||||
output.success(
|
output.success(
|
||||||
f"Hotfix '{name}' is voltooid: gemerged naar '{cfg.main_branch}' en '{cfg.develop_branch}' en getagd als '{tag_name}'."
|
f"Hotfix '{name}' is voltooid: gemerged naar '{cfg.main_branch}' en '{cfg.develop_branch}' en getagd als '{tag_name}'."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Optionele cleanup
|
||||||
|
if cfg.delete_hotfix_after_finish:
|
||||||
|
output.info(f"Opruimen van lokale hotfix branch '{hotfix_branch}'")
|
||||||
|
try:
|
||||||
|
git_api._run_git(["branch", "-d", hotfix_branch]) # type: ignore[attr-defined]
|
||||||
|
except git_api.GitError as exc:
|
||||||
|
output.warning(f"Kon hotfix branch '{hotfix_branch}' niet verwijderen: {exc}")
|
||||||
|
|
||||||
|
# Hooks na succesvolle hotfix-finish
|
||||||
|
hooks.run_hooks(
|
||||||
|
"hotfix_finish",
|
||||||
|
{
|
||||||
|
"branch": hotfix_branch,
|
||||||
|
"base_branch": cfg.main_branch,
|
||||||
|
"version": name,
|
||||||
|
"env": env,
|
||||||
|
"tag": tag_name,
|
||||||
|
},
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
except git_api.GitError as exc:
|
except git_api.GitError as exc:
|
||||||
output.error(str(exc))
|
output.error(str(exc))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from ..core import config, git_api, output
|
from ..core import config, git_api, hooks, output
|
||||||
|
|
||||||
|
|
||||||
def _release_branch_name(version: str) -> str:
|
def _release_branch_name(version: str) -> str:
|
||||||
@@ -31,6 +31,16 @@ def handle_release_start(args) -> int:
|
|||||||
output.info(f"Aanmaken van release branch '{branch_name}' vanaf '{cfg.develop_branch}'")
|
output.info(f"Aanmaken van release branch '{branch_name}' vanaf '{cfg.develop_branch}'")
|
||||||
git_api.create_branch(branch_name, cfg.develop_branch)
|
git_api.create_branch(branch_name, cfg.develop_branch)
|
||||||
output.success(f"Release branch '{branch_name}' is aangemaakt en gecheckt out.")
|
output.success(f"Release branch '{branch_name}' is aangemaakt en gecheckt out.")
|
||||||
|
|
||||||
|
# Hooks na succesvol aanmaken van een release branch
|
||||||
|
hooks.run_hooks(
|
||||||
|
"release_start",
|
||||||
|
{
|
||||||
|
"branch": branch_name,
|
||||||
|
"base_branch": cfg.develop_branch,
|
||||||
|
"version": version,
|
||||||
|
},
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
except git_api.GitError as exc:
|
except git_api.GitError as exc:
|
||||||
output.error(str(exc))
|
output.error(str(exc))
|
||||||
@@ -41,6 +51,7 @@ def handle_release_finish(args) -> int:
|
|||||||
cfg = config.CONFIG
|
cfg = config.CONFIG
|
||||||
try:
|
try:
|
||||||
version_arg = getattr(args, "version", None)
|
version_arg = getattr(args, "version", None)
|
||||||
|
env_arg = getattr(args, "env", None)
|
||||||
git_api.ensure_clean_working_tree()
|
git_api.ensure_clean_working_tree()
|
||||||
git_api.fetch_remote(cfg.remote_name)
|
git_api.fetch_remote(cfg.remote_name)
|
||||||
|
|
||||||
@@ -64,7 +75,11 @@ def handle_release_finish(args) -> int:
|
|||||||
git_api.ensure_not_behind_remote(cfg.main_branch, cfg.remote_name)
|
git_api.ensure_not_behind_remote(cfg.main_branch, cfg.remote_name)
|
||||||
git_api.ensure_not_behind_remote(cfg.develop_branch, cfg.remote_name)
|
git_api.ensure_not_behind_remote(cfg.develop_branch, cfg.remote_name)
|
||||||
|
|
||||||
tag_name = cfg.tag_format.format(version=version)
|
# Bepaal omgeving en uiteindelijke tagnaam
|
||||||
|
env = env_arg or cfg.release_default_env
|
||||||
|
suffix = cfg.environments.get(env, "")
|
||||||
|
base_tag = cfg.tag_format.format(version=version)
|
||||||
|
tag_name = f"{base_tag}{suffix}"
|
||||||
|
|
||||||
# Merge naar main
|
# Merge naar main
|
||||||
output.info(f"Mergen van '{release_branch}' naar '{cfg.main_branch}' en tag '{tag_name}' aanmaken")
|
output.info(f"Mergen van '{release_branch}' naar '{cfg.main_branch}' en tag '{tag_name}' aanmaken")
|
||||||
@@ -100,6 +115,26 @@ def handle_release_finish(args) -> int:
|
|||||||
output.success(
|
output.success(
|
||||||
f"Release '{version}' is voltooid: gemerged naar '{cfg.main_branch}' en '{cfg.develop_branch}' en getagd als '{tag_name}'."
|
f"Release '{version}' is voltooid: gemerged naar '{cfg.main_branch}' en '{cfg.develop_branch}' en getagd als '{tag_name}'."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Optionele cleanup van release branch
|
||||||
|
if cfg.delete_release_after_finish:
|
||||||
|
output.info(f"Opruimen van lokale release branch '{release_branch}'")
|
||||||
|
try:
|
||||||
|
git_api._run_git(["branch", "-d", release_branch]) # type: ignore[attr-defined]
|
||||||
|
except git_api.GitError as exc:
|
||||||
|
output.warning(f"Kon release branch '{release_branch}' niet verwijderen: {exc}")
|
||||||
|
|
||||||
|
# Hooks na succesvolle release-finish
|
||||||
|
hooks.run_hooks(
|
||||||
|
"release_finish",
|
||||||
|
{
|
||||||
|
"branch": release_branch,
|
||||||
|
"base_branch": cfg.main_branch,
|
||||||
|
"version": version,
|
||||||
|
"env": env,
|
||||||
|
"tag": tag_name,
|
||||||
|
},
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
except git_api.GitError as exc:
|
except git_api.GitError as exc:
|
||||||
output.error(str(exc))
|
output.error(str(exc))
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
try: # yaml is optioneel; bij ontbreken vallen we terug op defaults
|
||||||
|
import yaml # type: ignore[import]
|
||||||
|
except Exception: # pragma: no cover - defensieve fallback
|
||||||
|
yaml = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -17,6 +26,29 @@ class GitFlowConfig:
|
|||||||
use_no_ff_for_feature: bool = True
|
use_no_ff_for_feature: bool = True
|
||||||
use_no_ff_for_release: bool = True
|
use_no_ff_for_release: bool = True
|
||||||
use_no_ff_for_hotfix: bool = True
|
use_no_ff_for_hotfix: bool = True
|
||||||
|
# Globale dry-run vlag: wanneer True worden muterende git-acties niet
|
||||||
|
# echt uitgevoerd, maar enkel gelogd.
|
||||||
|
dry_run: bool = False
|
||||||
|
|
||||||
|
# Hooks per event (feature_start, feature_finish, ...)
|
||||||
|
hooks: Dict[str, List[str]] = field(default_factory=dict)
|
||||||
|
|
||||||
|
# Cleanup-instellingen: lokale branches verwijderen na succesvolle finish
|
||||||
|
delete_feature_after_finish: bool = False
|
||||||
|
delete_bugfix_after_finish: bool = False
|
||||||
|
delete_release_after_finish: bool = False
|
||||||
|
delete_hotfix_after_finish: bool = False
|
||||||
|
|
||||||
|
# Omgevingen voor tagging (suffixen) en defaults
|
||||||
|
environments: Dict[str, str] = field(
|
||||||
|
default_factory=lambda: {
|
||||||
|
"test": "-test",
|
||||||
|
"staging": "-staging",
|
||||||
|
"production": "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
release_default_env: str = "production"
|
||||||
|
hotfix_default_env: str = "production"
|
||||||
|
|
||||||
|
|
||||||
CONFIG = GitFlowConfig()
|
CONFIG = GitFlowConfig()
|
||||||
@@ -25,10 +57,98 @@ CONFIG = GitFlowConfig()
|
|||||||
def load_config() -> None:
|
def load_config() -> None:
|
||||||
"""Laad configuratie.
|
"""Laad configuratie.
|
||||||
|
|
||||||
Voor nu gebruiken we enkel harde defaults. Later kunnen we hier
|
We lezen optioneel `scripts/git/gitflow.yaml` in en overschrijven
|
||||||
een bestand (bv. yaml/toml) inlezen en `CONFIG` overschrijven.
|
velden in `CONFIG` op basis van de inhoud. Als het bestand of de
|
||||||
|
yaml-lib ontbreekt, blijven de defaults gelden.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: optioneel configuratiebestand ondersteunen.
|
if yaml is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Zoek het config-bestand relatief t.o.v. de huidige werkdirectory.
|
||||||
|
# We gaan ervan uit dat gitflow vanuit de projectroot draait (wrapper).
|
||||||
|
cfg_path = Path("scripts") / "git" / "gitflow.yaml"
|
||||||
|
if not cfg_path.is_file():
|
||||||
|
return None
|
||||||
|
|
||||||
|
with cfg_path.open("r", encoding="utf-8") as fh:
|
||||||
|
data = yaml.safe_load(fh) or {}
|
||||||
|
|
||||||
|
# Eenvoudige mapping: alleen bekende keys worden overgenomen.
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Basisvelden
|
||||||
|
CONFIG.main_branch = str(data.get("main_branch", CONFIG.main_branch))
|
||||||
|
CONFIG.develop_branch = str(data.get("develop_branch", CONFIG.develop_branch))
|
||||||
|
CONFIG.remote_name = str(data.get("remote_name", CONFIG.remote_name))
|
||||||
|
|
||||||
|
# Prefixes
|
||||||
|
prefixes = data.get("branch_prefixes") or {}
|
||||||
|
if isinstance(prefixes, dict):
|
||||||
|
CONFIG.feature_prefix = str(prefixes.get("feature", CONFIG.feature_prefix))
|
||||||
|
CONFIG.bugfix_prefix = str(prefixes.get("bugfix", CONFIG.bugfix_prefix))
|
||||||
|
CONFIG.hotfix_prefix = str(prefixes.get("hotfix", CONFIG.hotfix_prefix))
|
||||||
|
CONFIG.release_prefix = str(prefixes.get("release", CONFIG.release_prefix))
|
||||||
|
|
||||||
|
# Merge-strategieën
|
||||||
|
strategies = data.get("merge_strategies") or {}
|
||||||
|
if isinstance(strategies, dict):
|
||||||
|
CONFIG.use_no_ff_for_feature = strategies.get(
|
||||||
|
"feature_finish", "no-ff" if CONFIG.use_no_ff_for_feature else "ff"
|
||||||
|
) == "no-ff"
|
||||||
|
CONFIG.use_no_ff_for_release = strategies.get(
|
||||||
|
"release_finish", "no-ff" if CONFIG.use_no_ff_for_release else "ff"
|
||||||
|
) == "no-ff"
|
||||||
|
CONFIG.use_no_ff_for_hotfix = strategies.get(
|
||||||
|
"hotfix_finish", "no-ff" if CONFIG.use_no_ff_for_hotfix else "ff"
|
||||||
|
) == "no-ff"
|
||||||
|
|
||||||
|
# Tag-format
|
||||||
|
if "tag_format" in data:
|
||||||
|
CONFIG.tag_format = str(data["tag_format"])
|
||||||
|
|
||||||
|
# Cleanup-instellingen
|
||||||
|
cleanup = data.get("cleanup") or {}
|
||||||
|
if isinstance(cleanup, dict):
|
||||||
|
CONFIG.delete_feature_after_finish = bool(
|
||||||
|
cleanup.get("delete_feature_after_finish", CONFIG.delete_feature_after_finish)
|
||||||
|
)
|
||||||
|
CONFIG.delete_bugfix_after_finish = bool(
|
||||||
|
cleanup.get("delete_bugfix_after_finish", CONFIG.delete_bugfix_after_finish)
|
||||||
|
)
|
||||||
|
CONFIG.delete_release_after_finish = bool(
|
||||||
|
cleanup.get("delete_release_after_finish", CONFIG.delete_release_after_finish)
|
||||||
|
)
|
||||||
|
CONFIG.delete_hotfix_after_finish = bool(
|
||||||
|
cleanup.get("delete_hotfix_after_finish", CONFIG.delete_hotfix_after_finish)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Hooks
|
||||||
|
hooks = data.get("hooks") or {}
|
||||||
|
if isinstance(hooks, dict):
|
||||||
|
normalized: Dict[str, List[str]] = {}
|
||||||
|
for key, value in hooks.items():
|
||||||
|
if isinstance(value, list):
|
||||||
|
normalized[key] = [str(cmd) for cmd in value]
|
||||||
|
elif isinstance(value, str):
|
||||||
|
normalized[key] = [value]
|
||||||
|
CONFIG.hooks = normalized
|
||||||
|
|
||||||
|
# Omgevingen en defaults
|
||||||
|
envs = data.get("environments") or {}
|
||||||
|
if isinstance(envs, dict):
|
||||||
|
CONFIG.environments = {str(k): str(v) for k, v in envs.items()}
|
||||||
|
|
||||||
|
if "release_default_env" in data:
|
||||||
|
CONFIG.release_default_env = str(data["release_default_env"])
|
||||||
|
if "hotfix_default_env" in data:
|
||||||
|
CONFIG.hotfix_default_env = str(data["hotfix_default_env"])
|
||||||
|
|
||||||
|
# Dry-run kan ook via config gezet worden, maar CLI-flag heeft prioriteit;
|
||||||
|
# daarom overschrijven we hier niet expliciet als het al gezet is.
|
||||||
|
if "dry_run" in data and not CONFIG.dry_run:
|
||||||
|
CONFIG.dry_run = bool(data["dry_run"])
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
import subprocess
|
import subprocess
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from . import output
|
from . import config, output
|
||||||
|
|
||||||
|
|
||||||
class GitError(RuntimeError):
|
class GitError(RuntimeError):
|
||||||
@@ -12,6 +12,16 @@ class GitError(RuntimeError):
|
|||||||
|
|
||||||
def _run_git(args: Iterable[str], *, capture_output: bool = False, check: bool = True) -> subprocess.CompletedProcess:
|
def _run_git(args: Iterable[str], *, capture_output: bool = False, check: bool = True) -> subprocess.CompletedProcess:
|
||||||
cmd = ["git", *args]
|
cmd = ["git", *args]
|
||||||
|
# Dry-run ondersteuning: voer geen echte git-commando's uit, maar log enkel
|
||||||
|
# wat er zou gebeuren en geef een "geslaagde" CompletedProcess terug.
|
||||||
|
if config.CONFIG.dry_run:
|
||||||
|
output.info(f"[DRY-RUN] zou uitvoeren: {' '.join(cmd)}")
|
||||||
|
return subprocess.CompletedProcess(
|
||||||
|
cmd,
|
||||||
|
0,
|
||||||
|
stdout="" if capture_output else None,
|
||||||
|
stderr="",
|
||||||
|
)
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
cmd,
|
cmd,
|
||||||
text=True,
|
text=True,
|
||||||
@@ -92,3 +102,15 @@ def ensure_not_behind_remote(branch: str, remote: str) -> None:
|
|||||||
f"Doe eerst een 'git pull --ff-only' of werk te wijzigingen lokaal bij."
|
f"Doe eerst een 'git pull --ff-only' of werk te wijzigingen lokaal bij."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def list_local_branches_with_prefix(prefix: str) -> list[str]:
|
||||||
|
"""Geef een gesorteerde lijst van lokale branches die met het prefix starten."""
|
||||||
|
|
||||||
|
result = _run_git(
|
||||||
|
["for-each-ref", "--format=%(refname:short)", "refs/heads"],
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
lines = (result.stdout or "").splitlines()
|
||||||
|
branches = sorted(b for b in (ln.strip() for ln in lines) if b.startswith(prefix))
|
||||||
|
return branches
|
||||||
|
|
||||||
|
|||||||
43
scripts/git/core/hooks.py
Normal file
43
scripts/git/core/hooks.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from . import config, output
|
||||||
|
|
||||||
|
|
||||||
|
def run_hooks(event: str, context: Dict[str, str] | None = None) -> None:
|
||||||
|
"""Voer alle geconfigureerde hooks voor een gegeven event uit.
|
||||||
|
|
||||||
|
Hooks worden gedefinieerd in `CONFIG.hooks[event]` als een lijst van
|
||||||
|
shell-commando's. We geven context door via environment-variabelen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
hooks = config.CONFIG.hooks.get(event) or []
|
||||||
|
if not hooks:
|
||||||
|
return
|
||||||
|
|
||||||
|
base_env = os.environ.copy()
|
||||||
|
base_env.setdefault("GITFLOW_EVENT", event)
|
||||||
|
|
||||||
|
if context:
|
||||||
|
for key, value in context.items():
|
||||||
|
base_env[f"GITFLOW_{key.upper()}"] = value
|
||||||
|
|
||||||
|
for cmd in hooks:
|
||||||
|
# Eenvoudige logging
|
||||||
|
output.info(f"[HOOK {event}] {cmd}")
|
||||||
|
|
||||||
|
if config.CONFIG.dry_run:
|
||||||
|
output.info("[DRY-RUN] Hook niet echt uitgevoerd.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Gebruik shlex.split zodat eenvoudige strings netjes opgesplitst worden.
|
||||||
|
args = shlex.split(cmd)
|
||||||
|
result = subprocess.run(args, env=base_env, text=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Hook voor event '{event}' faalde met exitcode {result.returncode}: {cmd}"
|
||||||
|
)
|
||||||
@@ -29,6 +29,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||||||
description="Git Flow helper voor deze repo",
|
description="Git Flow helper voor deze repo",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
|
action="store_true",
|
||||||
|
help="Toon welke git-commando's uitgevoerd zouden worden, zonder echt te veranderen",
|
||||||
|
)
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
# status
|
# status
|
||||||
@@ -85,6 +91,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||||||
nargs="?",
|
nargs="?",
|
||||||
help="Versienummer (bijv. 1.2.0). Laat leeg om af te leiden van de huidige branch of interactief te vragen.",
|
help="Versienummer (bijv. 1.2.0). Laat leeg om af te leiden van de huidige branch of interactief te vragen.",
|
||||||
)
|
)
|
||||||
|
release_finish.add_argument(
|
||||||
|
"--env",
|
||||||
|
dest="env",
|
||||||
|
help="Omgeving voor tagging (bijv. test, staging, production). Laat leeg voor default uit config.",
|
||||||
|
)
|
||||||
release_finish.set_defaults(func=release_cmd.handle_release_finish)
|
release_finish.set_defaults(func=release_cmd.handle_release_finish)
|
||||||
|
|
||||||
# hotfix
|
# hotfix
|
||||||
@@ -101,6 +112,11 @@ def _build_parser() -> argparse.ArgumentParser:
|
|||||||
nargs="?",
|
nargs="?",
|
||||||
help="Naam of versie van de hotfix (zonder prefix). Laat leeg om huidige branch te gebruiken.",
|
help="Naam of versie van de hotfix (zonder prefix). Laat leeg om huidige branch te gebruiken.",
|
||||||
)
|
)
|
||||||
|
hotfix_finish.add_argument(
|
||||||
|
"--env",
|
||||||
|
dest="env",
|
||||||
|
help="Omgeving voor tagging (bijv. test, staging, production). Laat leeg voor default uit config.",
|
||||||
|
)
|
||||||
hotfix_finish.set_defaults(func=hotfix_cmd.handle_hotfix_finish)
|
hotfix_finish.set_defaults(func=hotfix_cmd.handle_hotfix_finish)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
@@ -110,8 +126,6 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv[1:]
|
argv = sys.argv[1:]
|
||||||
|
|
||||||
cfg.load_config() # voor nu alleen defaults, later evt. bestand
|
|
||||||
|
|
||||||
parser = _build_parser()
|
parser = _build_parser()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -119,6 +133,11 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
except SystemExit as exc: # argparse gebruikt SystemExit
|
except SystemExit as exc: # argparse gebruikt SystemExit
|
||||||
return exc.code
|
return exc.code
|
||||||
|
|
||||||
|
# Dry-run vlag doorgeven aan configuratie *voor* we commands uitvoeren
|
||||||
|
cfg.CONFIG.dry_run = bool(getattr(args, "dry_run", False))
|
||||||
|
|
||||||
|
cfg.load_config() # later kan dit config-bestand inladen en overrides toepassen
|
||||||
|
|
||||||
func = getattr(args, "func", None)
|
func = getattr(args, "func", None)
|
||||||
if func is None:
|
if func is None:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
|
|||||||
Reference in New Issue
Block a user