chore(repo): replace app symlink/submodule with in-repo app directory; enable committing app/Dockerfile and tooling\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

This commit is contained in:
torsten committed 2026-07-17 13:13:02 +02:00
1 parent a12270ad01
commit ae2c674b81
25 files changed
+3417 -1

No files matched your search

+204
View File
@@ -0,0 +1,204 @@
"""Batch orchestrator – coordinates the full migration of one batch.
Flow:
1. Read the prioritised file list (one path per line, in order).
2. Create git worktrees for Track A and Track B in parallel.
3. Run cmake --configure in each worktree.
4. Process files in priority order, with at most MAX_CONCURRENT_TASKS
workers per track. Both tracks run concurrently.
5. Commit a batch-summary to each branch.
6. Produce a comparison report (Track A vs Track B).
"""
from __future__ import annotations
import asyncio
import logging
import time
from pathlib import Path
import httpx
from migration_pipeline.build_runner import cmake_configure
from migration_pipeline.config import (
MAX_CONCURRENT_TASKS,
REPORTS_DIR,
TRACK_A_ID,
TRACK_B_ID,
WORKSPACE_ROOT,
)
from migration_pipeline.git_manager import (
commit_batch_summary,
setup_worktree,
)
from migration_pipeline.migrator import migrate_file
from migration_pipeline.models import BatchResult, FileResult, FileStatus, Track
from migration_pipeline.reporter import write_comparison_report
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# File-list loading
# ---------------------------------------------------------------------------
def load_file_list(list_path: Path, source_root: Path) -> list[str]:
"""Read the external priority file list.
Accepts:
- Plain text: one file path per line (blank lines and #-comments ignored).
- The paths may be absolute or relative to source_root.
"""
base = source_root.resolve()
files: list[str] = []
for raw in list_path.read_text().splitlines():
stripped = raw.strip()
if not stripped or stripped.startswith("#"):
continue
p = Path(stripped)
if not p.is_absolute():
p = base / p
files.append(str(p.resolve()))
return files
# ---------------------------------------------------------------------------
# Single-track worker pool
# ---------------------------------------------------------------------------
async def _run_track(
client: httpx.AsyncClient,
file_list: list[str],
source_root: Path,
track: Track,
worktree_root: Path,
build_dir: Path,
batch_id: str,
) -> BatchResult:
semaphore = asyncio.Semaphore(MAX_CONCURRENT_TASKS)
source_root = source_root.resolve()
async def _worker(fp: str) -> FileResult:
async with semaphore:
abs_file = Path(fp).resolve()
try:
rel_file = abs_file.relative_to(source_root)
except ValueError as exc:
raise ValueError(
f"File '{abs_file}' is not under source-root '{source_root}'"
) from exc
source_file = worktree_root / rel_file
return await migrate_file(
client=client,
source_file=source_file,
worktree_root=worktree_root,
build_dir=build_dir,
track=track,
batch_id=batch_id,
)
logger.info("[TRACK %s] Starting %d files", track.value, len(file_list))
results: list[FileResult] = await asyncio.gather(
*[_worker(fp) for fp in file_list]
)
successes = [r for r in results if r.status == FileStatus.SUCCESS.value]
quarantine = [r for r in results if r.status == FileStatus.QUARANTINED.value]
elapsed = [r.elapsed_seconds for r in results]
warnings = sum(r.gate.total_warnings for r in results)
return BatchResult(
batch_id=batch_id,
track=track.value,
branch=f"migration/{batch_id}/{track.value}",
total_files=len(results),
success_count=len(successes),
quarantine_count=len(quarantine),
total_warnings=warnings,
mean_elapsed_seconds=sum(elapsed) / len(elapsed) if elapsed else 0.0,
results=results,
)
# ---------------------------------------------------------------------------
# Public entry point
# ---------------------------------------------------------------------------
async def run_batch(
batch_id: str,
file_list_path: Path,
repo_root: Path,
source_root: Path,
) -> tuple[BatchResult, BatchResult]:
"""Run the full batch on both tracks in parallel.
Returns ``(result_track_a, result_track_b)``.
"""
file_list = load_file_list(file_list_path, source_root)
logger.info(
"[BATCH %s] Loaded %d files from %s",
batch_id, len(file_list), file_list_path,
)
workspace = WORKSPACE_ROOT.resolve()
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
# ---- Set up worktrees for both tracks ----------------------------------
worktree_a = setup_worktree(batch_id, TRACK_A_ID, repo_root, workspace)
worktree_b = setup_worktree(batch_id, TRACK_B_ID, repo_root, workspace)
build_dir_a = worktree_a / "build"
build_dir_b = worktree_b / "build"
# ---- Configure cmake in both worktrees (in parallel) ------------------
logger.info("[BATCH %s] Configuring cmake for both tracks...", batch_id)
await asyncio.gather(
cmake_configure(worktree_a, build_dir_a),
cmake_configure(worktree_b, build_dir_b),
)
# ---- Run both tracks concurrently -------------------------------------
async with httpx.AsyncClient() as client:
result_a, result_b = await asyncio.gather(
_run_track(
client, file_list, source_root, Track.A, worktree_a, build_dir_a, batch_id
),
_run_track(
client, file_list, source_root, Track.B, worktree_b, build_dir_b, batch_id
),
)
# ---- Final batch commit on each branch --------------------------------
commit_batch_summary(worktree_a, TRACK_A_ID, batch_id)
commit_batch_summary(worktree_b, TRACK_B_ID, batch_id)
# ---- Write comparison report ------------------------------------------
report_path = REPORTS_DIR / f"{batch_id}_comparison.md"
write_comparison_report(result_a, result_b, report_path)
logger.info("[BATCH %s] Report written to %s", batch_id, report_path)
_print_summary(result_a, result_b)
return result_a, result_b
# ---------------------------------------------------------------------------
# Console summary
# ---------------------------------------------------------------------------
def _print_summary(a: BatchResult, b: BatchResult) -> None:
print("\n" + "=" * 64)
print(f" Batch: {a.batch_id}")
print("=" * 64)
_print_track_row("Track A (konservativ)", a)
_print_track_row("Track B (modern) ", b)
print("=" * 64 + "\n")
def _print_track_row(label: str, r: BatchResult) -> None:
print(
f" {label}: "
f"{r.success_count}/{r.total_files} OK | "
f"{r.quarantine_count} quarantined | "
f"{r.total_warnings} warnings | "
f"avg {r.mean_elapsed_seconds:.0f}s"
)