Files
ankn a5f8fb49ab Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
2026-06-30 20:39:52 +02:00

183 lines
6.4 KiB
Python

"""
GitLab Branch-Analyse: Aktive Branches der Kern-Services.
Nutzt Projekt-IDs direkt (zuverlaessiger als Pfad-Encoding).
"""
import urllib.request, json, ssl
from datetime import datetime, timezone
from pathlib import Path
from collections import defaultdict
GITLAB_URL = "https://git.tech.rz.db.de"
API = f"{GITLAB_URL}/api/v4"
secrets = {}
with open("project-audit/.secrets", "r", encoding="utf-8-sig") as f:
for line in f:
if "=" in line and not line.startswith("#"):
k, v = line.strip().split("=", 1)
secrets[k.strip()] = v.strip()
TOKEN = secrets.get("GITLAB_TOKEN_BESTELLSYSTEM", "")
if not TOKEN:
print("FEHLER: GITLAB_TOKEN_BESTELLSYSTEM nicht in .secrets!")
exit(1)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
def api_get(path):
url = f"{API}{path}"
req = urllib.request.Request(url)
req.add_header("PRIVATE-TOKEN", TOKEN)
try:
resp = urllib.request.urlopen(req, context=ctx, timeout=30)
return json.loads(resp.read().decode("utf-8"))
except Exception as e:
return None
# Kern-Services mit IDs (aus gitlab-find-projects.py)
CORE_PROJECTS = {
"portal-middleware": 56664,
"portal-ui": 51502,
"steuerung-vertrieb": 54688,
"auftrags-verwaltung-trasse": 63436,
"common-interface": 52825,
"stammdaten-bereitstellung": 150168,
"tadef-connector": 352680,
"archivierungsservice": 294401,
"ifp-connector": 256070,
"kundendaten-bereitstellung": 109956,
"vertragsdaten-verteiler": 352409,
"rabattnummern-bereitstellung": 216978,
"taftap-tdm-konverter": 256243,
}
print("=== GitLab Branch-Analyse ===")
print(f"Datum: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
print(f"Projekte: {len(CORE_PROJECTS)}")
print()
now = datetime.now(timezone.utc)
all_branches = []
api_calls = 0
for proj_name, proj_id in CORE_PROJECTS.items():
# Branches laden (paginiert)
branches = []
page = 1
while True:
data = api_get(f"/projects/{proj_id}/repository/branches?per_page=100&page={page}")
api_calls += 1
if not data:
break
branches.extend(data)
if len(data) < 100:
break
page += 1
feature_branches = []
for b in branches:
name = b["name"]
if name in ("master", "main", "develop"):
continue
commit = b.get("commit", {})
committed_date = commit.get("committed_date", "")
author = commit.get("author_name", "?")
if committed_date:
try:
dt = datetime.fromisoformat(committed_date.replace("Z", "+00:00"))
age_days = (now - dt).days
except:
age_days = -1
else:
age_days = -1
feature_branches.append({
"project": proj_name,
"branch": name,
"author": author,
"age_days": age_days,
"date": committed_date[:10] if committed_date else "?",
})
all_branches.extend(feature_branches)
active = [b for b in feature_branches if 0 <= b["age_days"] <= 30]
stale = [b for b in feature_branches if b["age_days"] > 30]
print(f" {proj_name:<35} {len(feature_branches):>3} Branches ({len(active)} aktiv, {len(stale)} stale)")
print(f"\nAPI-Calls: {api_calls}")
print(f"Branches gesamt: {len(all_branches)}")
# Aktive Branches (letzte 30 Tage)
active_branches = [b for b in all_branches if 0 <= b["age_days"] <= 30]
active_branches.sort(key=lambda x: x["age_days"])
print(f"\n--- Aktive Branches (letzte 30 Tage): {len(active_branches)} ---")
print(f"{'Projekt':<30} {'Branch':<60} {'Autor':<25} {'Tage'}")
print("-" * 125)
for b in active_branches[:50]:
branch_short = b["branch"][:59]
print(f"{b['project']:<30} {branch_short:<60} {b['author']:<25} {b['age_days']}d")
# Stale Branches (>60 Tage)
stale_branches = [b for b in all_branches if b["age_days"] > 60]
stale_branches.sort(key=lambda x: x["age_days"], reverse=True)
print(f"\n--- Stale Branches (>60 Tage): {len(stale_branches)} ---")
if stale_branches:
print(f"{'Projekt':<30} {'Branch':<60} {'Tage'}")
print("-" * 95)
for b in stale_branches[:20]:
branch_short = b["branch"][:59]
print(f"{b['project']:<30} {branch_short:<60} {b['age_days']}d")
# Analyse: Wer arbeitet an was?
print(f"\n--- Aktivitaet pro Autor (letzte 30 Tage) ---")
authors = defaultdict(list)
for b in active_branches:
authors[b["author"]].append(b)
for author, branches in sorted(authors.items(), key=lambda x: -len(x[1]))[:20]:
projects = set(b["project"] for b in branches)
print(f" {author:<30} {len(branches):>2} Branches in: {', '.join(sorted(projects))}")
# Spring Boot 4 Branches
sb4_branches = [b for b in all_branches if "spring" in b["branch"].lower() or "boot-4" in b["branch"].lower() or "springboot" in b["branch"].lower()]
print(f"\n--- Spring Boot 4 Branches: {len(sb4_branches)} ---")
for b in sorted(sb4_branches, key=lambda x: x["age_days"]):
status = "AKTIV" if b["age_days"] <= 30 else f"STALE ({b['age_days']}d)"
print(f" {b['project']:<30} {b['branch']:<55} {status}")
# Renovate Branches
renovate_branches = [b for b in all_branches if "renovate" in b["branch"].lower()]
renovate_active = [b for b in renovate_branches if b["age_days"] <= 7]
renovate_stale = [b for b in renovate_branches if b["age_days"] > 30]
print(f"\n--- Renovate Branches: {len(renovate_branches)} gesamt ---")
print(f" Frisch (<=7d): {len(renovate_active)}")
print(f" Stale (>30d): {len(renovate_stale)}")
# Ticket-Extraktion aus Branch-Namen
print(f"\n--- Jira-Tickets in aktiven Branches ---")
import re
tickets = defaultdict(list)
for b in active_branches:
matches = re.findall(r'(O2C[A-Z]+-\d+|TTTI-\d+)', b["branch"].upper())
for ticket in matches:
tickets[ticket].append(b)
if tickets:
for ticket, branches in sorted(tickets.items()):
projs = set(b["project"] for b in branches)
print(f" {ticket:<15} in {', '.join(sorted(projs))}")
# CSV Export
csv_path = Path("project-audit/data/gitlab-branches.csv")
with open(csv_path, "w", encoding="utf-8") as f:
f.write('"Project";"Branch";"Author";"LastCommit";"AgeDays"\n')
for b in sorted(all_branches, key=lambda x: (x["project"], x["age_days"])):
f.write(f'"{b["project"]}";"{b["branch"]}";"{b["author"]}";"{b["date"]}";"{b["age_days"]}"\n')
print(f"\nCSV exportiert: {csv_path}")