Merge branch 'feature/Convert_Git_Flow_Process_to_own_scripts' into develop
This commit is contained in:
0
scripts/__init__.py
Normal file
0
scripts/__init__.py
Normal file
0
scripts/git/__init__.py
Normal file
0
scripts/git/__init__.py
Normal file
0
scripts/git/commands/__init__.py
Normal file
0
scripts/git/commands/__init__.py
Normal file
109
scripts/git/commands/bugfix.py
Normal file
109
scripts/git/commands/bugfix.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..core import config, git_api, hooks, output
|
||||
|
||||
|
||||
def _bugfix_branch_name(name: str) -> str:
|
||||
return f"{config.CONFIG.bugfix_prefix}{name}"
|
||||
|
||||
|
||||
def handle_bugfix_start(args) -> int:
|
||||
name: str = args.name
|
||||
cfg = config.CONFIG
|
||||
|
||||
try:
|
||||
git_api.ensure_clean_working_tree()
|
||||
git_api.fetch_remote(cfg.remote_name)
|
||||
git_api.ensure_not_behind_remote(cfg.develop_branch, cfg.remote_name)
|
||||
|
||||
branch_name = _bugfix_branch_name(name)
|
||||
output.info(f"Aanmaken van bugfix branch '{branch_name}' vanaf '{cfg.develop_branch}'")
|
||||
git_api.create_branch(branch_name, cfg.develop_branch)
|
||||
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
|
||||
except git_api.GitError as exc:
|
||||
output.error(str(exc))
|
||||
return 1
|
||||
|
||||
|
||||
def handle_bugfix_finish(args) -> int:
|
||||
cfg = config.CONFIG
|
||||
name: str | None = args.name
|
||||
|
||||
try:
|
||||
git_api.ensure_clean_working_tree()
|
||||
git_api.fetch_remote(cfg.remote_name)
|
||||
|
||||
if name is None:
|
||||
current = git_api.get_current_branch()
|
||||
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(
|
||||
"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>."
|
||||
)
|
||||
else:
|
||||
bugfix_branch = _bugfix_branch_name(name)
|
||||
|
||||
git_api.ensure_not_behind_remote(bugfix_branch, cfg.remote_name)
|
||||
git_api.ensure_not_behind_remote(cfg.develop_branch, cfg.remote_name)
|
||||
|
||||
output.info(f"Mergen van '{bugfix_branch}' naar '{cfg.develop_branch}'")
|
||||
git_api.checkout_branch(cfg.develop_branch)
|
||||
|
||||
merge_args = ["merge"]
|
||||
if cfg.use_no_ff_for_feature:
|
||||
merge_args.append("--no-ff")
|
||||
merge_args.append(bugfix_branch)
|
||||
|
||||
try:
|
||||
git_api._run_git(merge_args) # type: ignore[attr-defined]
|
||||
except git_api.GitError as exc:
|
||||
raise git_api.GitError(
|
||||
"Merge is mislukt (mogelijk conflicten). Los de conflicten op en voltooi de merge handmatig."
|
||||
) from exc
|
||||
|
||||
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
|
||||
except git_api.GitError as exc:
|
||||
output.error(str(exc))
|
||||
return 1
|
||||
|
||||
112
scripts/git/commands/feature.py
Normal file
112
scripts/git/commands/feature.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..core import config, git_api, hooks, output
|
||||
|
||||
|
||||
def _feature_branch_name(name: str) -> str:
|
||||
return f"{config.CONFIG.feature_prefix}{name}"
|
||||
|
||||
|
||||
def handle_feature_start(args) -> int:
|
||||
name: str = args.name
|
||||
cfg = config.CONFIG
|
||||
|
||||
try:
|
||||
git_api.ensure_clean_working_tree()
|
||||
git_api.fetch_remote(cfg.remote_name)
|
||||
git_api.ensure_not_behind_remote(cfg.develop_branch, cfg.remote_name)
|
||||
|
||||
branch_name = _feature_branch_name(name)
|
||||
output.info(f"Aanmaken van feature branch '{branch_name}' vanaf '{cfg.develop_branch}'")
|
||||
git_api.create_branch(branch_name, cfg.develop_branch)
|
||||
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
|
||||
except git_api.GitError as exc:
|
||||
output.error(str(exc))
|
||||
return 1
|
||||
|
||||
|
||||
def handle_feature_finish(args) -> int:
|
||||
cfg = config.CONFIG
|
||||
name: str | None = args.name
|
||||
|
||||
try:
|
||||
git_api.ensure_clean_working_tree()
|
||||
git_api.fetch_remote(cfg.remote_name)
|
||||
|
||||
if name is None:
|
||||
current = git_api.get_current_branch()
|
||||
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(
|
||||
"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>."
|
||||
)
|
||||
else:
|
||||
feature_branch = _feature_branch_name(name)
|
||||
|
||||
# Zorg dat betrokken branches niet achterlopen op remote
|
||||
git_api.ensure_not_behind_remote(feature_branch, cfg.remote_name)
|
||||
git_api.ensure_not_behind_remote(cfg.develop_branch, cfg.remote_name)
|
||||
|
||||
output.info(f"Mergen van '{feature_branch}' naar '{cfg.develop_branch}'")
|
||||
git_api.checkout_branch(cfg.develop_branch)
|
||||
|
||||
merge_args = ["merge"]
|
||||
if cfg.use_no_ff_for_feature:
|
||||
merge_args.append("--no-ff")
|
||||
merge_args.append(feature_branch)
|
||||
|
||||
try:
|
||||
git_api._run_git(merge_args) # type: ignore[attr-defined]
|
||||
except git_api.GitError as exc:
|
||||
raise git_api.GitError(
|
||||
"Merge is mislukt (mogelijk conflicten). Los de conflicten op en voltooi de merge handmatig."
|
||||
) from exc
|
||||
|
||||
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
|
||||
except git_api.GitError as exc:
|
||||
output.error(str(exc))
|
||||
return 1
|
||||
|
||||
154
scripts/git/commands/hotfix.py
Normal file
154
scripts/git/commands/hotfix.py
Normal file
@@ -0,0 +1,154 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..core import config, git_api, hooks, output
|
||||
|
||||
|
||||
def _hotfix_branch_name(name: str) -> str:
|
||||
return f"{config.CONFIG.hotfix_prefix}{name}"
|
||||
|
||||
|
||||
def _ensure_version(name: str | None) -> str:
|
||||
# Voor hotfix kunnen we dezelfde prompt gebruiken indien geen naam/versie is opgegeven
|
||||
if name:
|
||||
return name
|
||||
output.info("Geen hotfix-naam/versie opgegeven. Gelieve een identificatie in te geven (bijv. 1.2.1 of short-name):")
|
||||
entered = input("Hotfix: ").strip()
|
||||
if not entered:
|
||||
raise git_api.GitError("Geen hotfix-naam/versie opgegeven.")
|
||||
return entered
|
||||
|
||||
|
||||
def handle_hotfix_start(args) -> int:
|
||||
cfg = config.CONFIG
|
||||
try:
|
||||
raw_name: str = args.name
|
||||
name = _ensure_version(raw_name)
|
||||
branch_name = _hotfix_branch_name(name)
|
||||
|
||||
git_api.ensure_clean_working_tree()
|
||||
git_api.fetch_remote(cfg.remote_name)
|
||||
git_api.ensure_not_behind_remote(cfg.main_branch, cfg.remote_name)
|
||||
|
||||
output.info(f"Aanmaken van hotfix branch '{branch_name}' vanaf '{cfg.main_branch}'")
|
||||
git_api.create_branch(branch_name, cfg.main_branch)
|
||||
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
|
||||
except git_api.GitError as exc:
|
||||
output.error(str(exc))
|
||||
return 1
|
||||
|
||||
|
||||
def handle_hotfix_finish(args) -> int:
|
||||
cfg = config.CONFIG
|
||||
try:
|
||||
name_arg = getattr(args, "name", None)
|
||||
env_arg = getattr(args, "env", None)
|
||||
|
||||
git_api.ensure_clean_working_tree()
|
||||
git_api.fetch_remote(cfg.remote_name)
|
||||
|
||||
if name_arg:
|
||||
name = name_arg
|
||||
hotfix_branch = _hotfix_branch_name(name)
|
||||
else:
|
||||
current = git_api.get_current_branch()
|
||||
prefix = cfg.hotfix_prefix
|
||||
if current.startswith(prefix):
|
||||
name = current[len(prefix) :]
|
||||
hotfix_branch = current
|
||||
else:
|
||||
# Toon lijst van beschikbare hotfix branches
|
||||
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(cfg.main_branch, cfg.remote_name)
|
||||
git_api.ensure_not_behind_remote(cfg.develop_branch, cfg.remote_name)
|
||||
|
||||
# Bepaal omgeving en tagnaam
|
||||
env = env_arg or cfg.hotfix_default_env
|
||||
suffix = cfg.environments.get(env, "")
|
||||
base_tag = cfg.tag_format.format(version=name)
|
||||
tag_name = f"{base_tag}{suffix}"
|
||||
|
||||
# Merge naar main
|
||||
output.info(f"Mergen van '{hotfix_branch}' naar '{cfg.main_branch}' en tag '{tag_name}' aanmaken")
|
||||
git_api.checkout_branch(cfg.main_branch)
|
||||
merge_args = ["merge"]
|
||||
if cfg.use_no_ff_for_hotfix:
|
||||
merge_args.append("--no-ff")
|
||||
merge_args.append(hotfix_branch)
|
||||
try:
|
||||
git_api._run_git(merge_args) # type: ignore[attr-defined]
|
||||
except git_api.GitError as exc:
|
||||
raise git_api.GitError(
|
||||
"Merge van hotfix naar main is mislukt (mogelijk conflicten). Los de conflicten op en voltooi de merge handmatig."
|
||||
) from exc
|
||||
|
||||
git_api._run_git(["tag", tag_name]) # type: ignore[attr-defined]
|
||||
|
||||
# Merge naar develop
|
||||
output.info(f"Mergen van '{hotfix_branch}' naar '{cfg.develop_branch}'")
|
||||
git_api.checkout_branch(cfg.develop_branch)
|
||||
merge_args = ["merge"]
|
||||
if cfg.use_no_ff_for_hotfix:
|
||||
merge_args.append("--no-ff")
|
||||
merge_args.append(hotfix_branch)
|
||||
try:
|
||||
git_api._run_git(merge_args) # type: ignore[attr-defined]
|
||||
except git_api.GitError as exc:
|
||||
raise git_api.GitError(
|
||||
"Merge van hotfix naar develop is mislukt (mogelijk conflicten). Los de conflicten op en voltooi de merge handmatig."
|
||||
) from exc
|
||||
|
||||
output.success(
|
||||
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
|
||||
except git_api.GitError as exc:
|
||||
output.error(str(exc))
|
||||
return 1
|
||||
|
||||
142
scripts/git/commands/release.py
Normal file
142
scripts/git/commands/release.py
Normal file
@@ -0,0 +1,142 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..core import config, git_api, hooks, output
|
||||
|
||||
|
||||
def _release_branch_name(version: str) -> str:
|
||||
return f"{config.CONFIG.release_prefix}{version}"
|
||||
|
||||
|
||||
def _ensure_version(version: str | None) -> str:
|
||||
if version:
|
||||
return version
|
||||
# Eenvoudige interactieve prompt; later kunnen we validatie uitbreiden
|
||||
output.info("Geen versie opgegeven. Gelieve een versie in te geven (bijv. 1.2.0):")
|
||||
entered = input("Versie: ").strip()
|
||||
if not entered:
|
||||
raise git_api.GitError("Geen versie opgegeven.")
|
||||
return entered
|
||||
|
||||
|
||||
def handle_release_start(args) -> int:
|
||||
cfg = config.CONFIG
|
||||
try:
|
||||
version = _ensure_version(getattr(args, "version", None))
|
||||
branch_name = _release_branch_name(version)
|
||||
|
||||
git_api.ensure_clean_working_tree()
|
||||
git_api.fetch_remote(cfg.remote_name)
|
||||
git_api.ensure_not_behind_remote(cfg.develop_branch, cfg.remote_name)
|
||||
|
||||
output.info(f"Aanmaken van release branch '{branch_name}' vanaf '{cfg.develop_branch}'")
|
||||
git_api.create_branch(branch_name, cfg.develop_branch)
|
||||
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
|
||||
except git_api.GitError as exc:
|
||||
output.error(str(exc))
|
||||
return 1
|
||||
|
||||
|
||||
def handle_release_finish(args) -> int:
|
||||
cfg = config.CONFIG
|
||||
try:
|
||||
version_arg = getattr(args, "version", None)
|
||||
env_arg = getattr(args, "env", None)
|
||||
git_api.ensure_clean_working_tree()
|
||||
git_api.fetch_remote(cfg.remote_name)
|
||||
|
||||
if version_arg:
|
||||
version = version_arg
|
||||
release_branch = _release_branch_name(version)
|
||||
else:
|
||||
# Probeer huidige branch te gebruiken
|
||||
current = git_api.get_current_branch()
|
||||
prefix = cfg.release_prefix
|
||||
if current.startswith(prefix):
|
||||
version = current[len(prefix) :]
|
||||
release_branch = current
|
||||
else:
|
||||
# Geen logische branch, vraag versie interactief
|
||||
version = _ensure_version(None)
|
||||
release_branch = _release_branch_name(version)
|
||||
|
||||
# Zorg dat betrokken branches niet achterlopen
|
||||
git_api.ensure_not_behind_remote(release_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)
|
||||
|
||||
# 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
|
||||
output.info(f"Mergen van '{release_branch}' naar '{cfg.main_branch}' en tag '{tag_name}' aanmaken")
|
||||
git_api.checkout_branch(cfg.main_branch)
|
||||
merge_args = ["merge"]
|
||||
if cfg.use_no_ff_for_release:
|
||||
merge_args.append("--no-ff")
|
||||
merge_args.append(release_branch)
|
||||
try:
|
||||
git_api._run_git(merge_args) # type: ignore[attr-defined]
|
||||
except git_api.GitError as exc:
|
||||
raise git_api.GitError(
|
||||
"Merge naar main is mislukt (mogelijk conflicten). Los de conflicten op en voltooi de merge handmatig."
|
||||
) from exc
|
||||
|
||||
# Tag aanmaken op main
|
||||
git_api._run_git(["tag", tag_name]) # type: ignore[attr-defined]
|
||||
|
||||
# Merge terug naar develop
|
||||
output.info(f"Mergen van '{release_branch}' terug naar '{cfg.develop_branch}'")
|
||||
git_api.checkout_branch(cfg.develop_branch)
|
||||
merge_args = ["merge"]
|
||||
if cfg.use_no_ff_for_release:
|
||||
merge_args.append("--no-ff")
|
||||
merge_args.append(release_branch)
|
||||
try:
|
||||
git_api._run_git(merge_args) # type: ignore[attr-defined]
|
||||
except git_api.GitError as exc:
|
||||
raise git_api.GitError(
|
||||
"Merge naar develop is mislukt (mogelijk conflicten). Los de conflicten op en voltooi de merge handmatig."
|
||||
) from exc
|
||||
|
||||
output.success(
|
||||
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
|
||||
except git_api.GitError as exc:
|
||||
output.error(str(exc))
|
||||
return 1
|
||||
|
||||
30
scripts/git/commands/status.py
Normal file
30
scripts/git/commands/status.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..core import config, git_api, output
|
||||
|
||||
|
||||
def handle_status(args) -> int: # noqa: ARG001 - argparse API
|
||||
"""Toon huidige branch en eenvoudige status-info."""
|
||||
|
||||
try:
|
||||
branch = git_api.get_current_branch()
|
||||
except git_api.GitError as exc:
|
||||
output.error(str(exc))
|
||||
return 1
|
||||
|
||||
clean = git_api.is_clean_working_tree()
|
||||
|
||||
output.heading("Repo status")
|
||||
output.plain(f"Huidige branch : {branch}")
|
||||
output.plain(f"Working tree : {'clean' if clean else 'NIET clean'}")
|
||||
|
||||
# Optionele remote-checks
|
||||
cfg = config.CONFIG
|
||||
for important_branch in {cfg.main_branch, cfg.develop_branch, branch}:
|
||||
try:
|
||||
git_api.ensure_not_behind_remote(important_branch, cfg.remote_name)
|
||||
except git_api.GitError as exc:
|
||||
output.warning(str(exc))
|
||||
|
||||
return 0
|
||||
|
||||
0
scripts/git/core/__init__.py
Normal file
0
scripts/git/core/__init__.py
Normal file
154
scripts/git/core/config.py
Normal file
154
scripts/git/core/config.py
Normal file
@@ -0,0 +1,154 @@
|
||||
from __future__ import annotations
|
||||
|
||||
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
|
||||
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
|
||||
# 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()
|
||||
|
||||
|
||||
def load_config() -> None:
|
||||
"""Laad configuratie.
|
||||
|
||||
We lezen optioneel `scripts/git/gitflow.yaml` in en overschrijven
|
||||
velden in `CONFIG` op basis van de inhoud. Als het bestand of de
|
||||
yaml-lib ontbreekt, blijven de defaults gelden.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
116
scripts/git/core/git_api.py
Normal file
116
scripts/git/core/git_api.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from typing import Iterable
|
||||
|
||||
from . import config, output
|
||||
|
||||
|
||||
class GitError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def _run_git(args: Iterable[str], *, capture_output: bool = False, check: bool = True) -> subprocess.CompletedProcess:
|
||||
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(
|
||||
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."
|
||||
)
|
||||
|
||||
|
||||
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}"
|
||||
)
|
||||
61
scripts/git/core/output.py
Normal file
61
scripts/git/core/output.py
Normal 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}]")
|
||||
|
||||
10
scripts/git/gitflow
Executable file
10
scripts/git/gitflow
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Kleine wrapper zodat je gewoon `scripts/git/gitflow ...` kunt aanroepen
|
||||
# zonder expliciet `python` te typen.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
|
||||
|
||||
cd "${PROJECT_ROOT}" || exit 1
|
||||
exec python -m scripts.git.gitflow "$@"
|
||||
154
scripts/git/gitflow.py
Normal file
154
scripts/git/gitflow.py
Normal file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python
|
||||
"""Git Flow helper CLI for this repository.
|
||||
|
||||
Eerste versie:
|
||||
- status
|
||||
- feature start/finish
|
||||
- bugfix start/finish
|
||||
|
||||
Andere flows (release/hotfix, hooks, enz.) volgen later.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from .core import config as cfg
|
||||
from .core import output
|
||||
from .commands import status as status_cmd
|
||||
from .commands import feature as feature_cmd
|
||||
from .commands import bugfix as bugfix_cmd
|
||||
from .commands import release as release_cmd
|
||||
from .commands import hotfix as hotfix_cmd
|
||||
|
||||
|
||||
def _build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="gitflow",
|
||||
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)
|
||||
|
||||
# status
|
||||
status_parser = subparsers.add_parser("status", help="Toon huidige branch en repo-status")
|
||||
status_parser.set_defaults(func=status_cmd.handle_status)
|
||||
|
||||
# feature
|
||||
feature_parser = subparsers.add_parser("feature", help="Feature branches beheren")
|
||||
feature_sub = feature_parser.add_subparsers(dest="action", required=True)
|
||||
|
||||
feature_start = feature_sub.add_parser("start", help="Start een nieuwe feature branch vanaf develop")
|
||||
feature_start.add_argument("name", help="Naam van de feature (zonder prefix)")
|
||||
feature_start.set_defaults(func=feature_cmd.handle_feature_start)
|
||||
|
||||
feature_finish = feature_sub.add_parser("finish", help="Merge een feature branch terug naar develop")
|
||||
feature_finish.add_argument(
|
||||
"name",
|
||||
nargs="?",
|
||||
help="Naam van de feature (zonder prefix). Laat leeg om huidige branch te gebruiken.",
|
||||
)
|
||||
feature_finish.set_defaults(func=feature_cmd.handle_feature_finish)
|
||||
|
||||
# bugfix
|
||||
bugfix_parser = subparsers.add_parser("bugfix", help="Bugfix branches beheren (op develop)")
|
||||
bugfix_sub = bugfix_parser.add_subparsers(dest="action", required=True)
|
||||
|
||||
bugfix_start = bugfix_sub.add_parser("start", help="Start een nieuwe bugfix branch vanaf develop")
|
||||
bugfix_start.add_argument("name", help="Naam van de bugfix (zonder prefix)")
|
||||
bugfix_start.set_defaults(func=bugfix_cmd.handle_bugfix_start)
|
||||
|
||||
bugfix_finish = bugfix_sub.add_parser("finish", help="Merge een bugfix branch terug naar develop")
|
||||
bugfix_finish.add_argument(
|
||||
"name",
|
||||
nargs="?",
|
||||
help="Naam van de bugfix (zonder prefix). Laat leeg om huidige branch te gebruiken.",
|
||||
)
|
||||
bugfix_finish.set_defaults(func=bugfix_cmd.handle_bugfix_finish)
|
||||
|
||||
# release
|
||||
release_parser = subparsers.add_parser("release", help="Release branches beheren (main <-> develop)")
|
||||
release_sub = release_parser.add_subparsers(dest="action", required=True)
|
||||
|
||||
release_start = release_sub.add_parser("start", help="Start een nieuwe release branch vanaf develop")
|
||||
release_start.add_argument(
|
||||
"version",
|
||||
nargs="?",
|
||||
help="Versienummer (bijv. 1.2.0). Laat leeg om interactief in te geven.",
|
||||
)
|
||||
release_start.set_defaults(func=release_cmd.handle_release_start)
|
||||
|
||||
release_finish = release_sub.add_parser("finish", help="Voltooi een release: merge naar main en develop + tag")
|
||||
release_finish.add_argument(
|
||||
"version",
|
||||
nargs="?",
|
||||
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)
|
||||
|
||||
# hotfix
|
||||
hotfix_parser = subparsers.add_parser("hotfix", help="Hotfix branches beheren (vanaf main)")
|
||||
hotfix_sub = hotfix_parser.add_subparsers(dest="action", required=True)
|
||||
|
||||
hotfix_start = hotfix_sub.add_parser("start", help="Start een nieuwe hotfix branch vanaf main")
|
||||
hotfix_start.add_argument("name", help="Naam of versie van de hotfix (zonder prefix)")
|
||||
hotfix_start.set_defaults(func=hotfix_cmd.handle_hotfix_start)
|
||||
|
||||
hotfix_finish = hotfix_sub.add_parser("finish", help="Voltooi een hotfix: merge naar main en develop + tag")
|
||||
hotfix_finish.add_argument(
|
||||
"name",
|
||||
nargs="?",
|
||||
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)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
parser = _build_parser()
|
||||
|
||||
try:
|
||||
args = parser.parse_args(argv)
|
||||
except SystemExit as exc: # argparse gebruikt SystemExit
|
||||
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)
|
||||
if func is None:
|
||||
parser.print_help()
|
||||
return 1
|
||||
|
||||
try:
|
||||
return func(args)
|
||||
except KeyboardInterrupt:
|
||||
output.error("Afgebroken door gebruiker (Ctrl+C)")
|
||||
return 130
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover - directe CLI entry
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user