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.
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
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}")
|
||||
Reference in New Issue
Block a user