Skip to content

Commit a63f811

Browse files
Pigbibicodex
andcommitted
feat(qsl): distinguish default branch workspace reports
Co-Authored-By: Codex <noreply@openai.com>
1 parent 55375b0 commit a63f811

2 files changed

Lines changed: 124 additions & 2 deletions

File tree

python/scripts/qslctl.py

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,35 @@ class RepoCheckResult:
5757
tier: str
5858
upgrade_ring: str
5959
enforce_bundle: bool
60+
checkout_branch: str | None
61+
default_branch: str | None
62+
63+
64+
def _git_output(repo_dir: Path, *args: str) -> str | None:
65+
try:
66+
value = subprocess.check_output(
67+
["git", "-C", str(repo_dir), *args],
68+
text=True,
69+
stderr=subprocess.DEVNULL,
70+
).strip()
71+
except (subprocess.CalledProcessError, FileNotFoundError):
72+
return None
73+
return value or None
74+
75+
76+
def _checkout_context(repo_dir: Path) -> tuple[str | None, str | None]:
77+
"""Return the local branch and configured origin default without fetching.
78+
79+
A workspace report is intentionally a local-checkout view. The optional
80+
mainline-only mode must therefore distinguish a feature/archive checkout
81+
from a locally checked-out default branch, without claiming either is
82+
current with remote GitHub state.
83+
"""
84+
85+
branch = _git_output(repo_dir, "symbolic-ref", "--quiet", "--short", "HEAD")
86+
origin_head = _git_output(repo_dir, "symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD")
87+
default_branch = origin_head.removeprefix("origin/") if origin_head else "main"
88+
return branch, default_branch
6089

6190

6291
def _is_quant_repo(repo_dir: Path) -> bool:
@@ -86,6 +115,7 @@ def iter_qsl_repos(projects_root: Path) -> list[Path]:
86115

87116

88117
def check_repo(repo_root: Path, compat_root: Path) -> RepoCheckResult:
118+
checkout_branch, default_branch = _checkout_context(repo_root)
89119
try:
90120
ok, issues, warnings, notes = check_qsl_compat._check(repo_root=repo_root, compat_root=compat_root)
91121
qsl_cfg = check_qsl_compat._load_qsl_config(repo_root)
@@ -106,6 +136,8 @@ def check_repo(repo_root: Path, compat_root: Path) -> RepoCheckResult:
106136
tier=str(qsl_cfg["tier"]),
107137
upgrade_ring=str(qsl_cfg["upgrade_ring"]),
108138
enforce_bundle=bool(qsl_cfg["enforce_bundle"]),
139+
checkout_branch=checkout_branch,
140+
default_branch=default_branch,
109141
)
110142

111143

@@ -125,6 +157,9 @@ def _result_payload(result: RepoCheckResult) -> dict[str, Any]:
125157
"tier": result.tier,
126158
"upgrade_ring": result.upgrade_ring,
127159
"enforce_bundle": result.enforce_bundle,
160+
"checkout_branch": result.checkout_branch,
161+
"default_branch": result.default_branch,
162+
"is_default_branch_checkout": result.checkout_branch == result.default_branch,
128163
}
129164

130165

@@ -240,6 +275,7 @@ def _workspace_report(results: list[RepoCheckResult], compat_root: Path) -> dict
240275
}
241276
for ring in ring_order
242277
}
278+
243279
issue_counts: Counter[str] = Counter()
244280
package_hotspots: Counter[tuple[str, str]] = Counter()
245281

@@ -297,6 +333,27 @@ def _workspace_report(results: list[RepoCheckResult], compat_root: Path) -> dict
297333
}
298334

299335

336+
def _default_branch_results(results: list[RepoCheckResult]) -> tuple[list[RepoCheckResult], list[RepoCheckResult]]:
337+
included = [result for result in results if result.checkout_branch == result.default_branch]
338+
excluded = [result for result in results if result not in included]
339+
return included, excluded
340+
341+
342+
def _with_workspace_scope(
343+
report: dict[str, Any], *, mainline_only: bool, excluded: list[RepoCheckResult]
344+
) -> dict[str, Any]:
345+
report["scope"] = "local_default_branch_checkouts" if mainline_only else "all_local_checkouts"
346+
report["excluded_nondefault_checkouts"] = [
347+
{
348+
"repo": result.repo,
349+
"checkout_branch": result.checkout_branch,
350+
"default_branch": result.default_branch,
351+
}
352+
for result in excluded
353+
]
354+
return report
355+
356+
300357
def _workspace_plan(report: dict[str, Any]) -> dict[str, Any]:
301358
phases: list[dict[str, Any]] = []
302359
for ring in report["rings"]:
@@ -334,6 +391,7 @@ def _workspace_plan(report: dict[str, Any]) -> dict[str, Any]:
334391

335392

336393
def _print_report(report: dict[str, Any]) -> None:
394+
print(f"Scope: {report.get('scope', 'all_local_checkouts')}")
337395
print(
338396
"QSL workspace report: "
339397
f"repos={report['total_repositories']} "
@@ -357,6 +415,11 @@ def _print_report(report: dict[str, Any]) -> None:
357415
print("Hotspots:")
358416
for hotspot in report["bundle_hotspots"]:
359417
print(f" {hotspot['package']} @ {hotspot['source']}: {hotspot['count']}")
418+
excluded = report.get("excluded_nondefault_checkouts", [])
419+
if excluded:
420+
print("Excluded non-default checkouts:")
421+
for item in excluded:
422+
print(f" {item['repo']}: {item['checkout_branch'] or 'DETACHED'} (default {item['default_branch']})")
360423

361424

362425
def _print_plan(plan: dict[str, Any]) -> None:
@@ -416,8 +479,10 @@ def _cmd_check_all(args: argparse.Namespace) -> int:
416479

417480

418481
def _cmd_report(args: argparse.Namespace) -> int:
419-
results = check_all(projects_root=args.projects_root.resolve(), compat_root=args.compat_root.resolve())
482+
all_results = check_all(projects_root=args.projects_root.resolve(), compat_root=args.compat_root.resolve())
483+
results, excluded = _default_branch_results(all_results) if args.mainline_only else (all_results, [])
420484
report = _workspace_report(results, compat_root=args.compat_root.resolve())
485+
_with_workspace_scope(report, mainline_only=args.mainline_only, excluded=excluded)
421486
if args.json:
422487
print(json.dumps(report, ensure_ascii=False, indent=2))
423488
else:
@@ -426,9 +491,13 @@ def _cmd_report(args: argparse.Namespace) -> int:
426491

427492

428493
def _cmd_plan(args: argparse.Namespace) -> int:
429-
results = check_all(projects_root=args.projects_root.resolve(), compat_root=args.compat_root.resolve())
494+
all_results = check_all(projects_root=args.projects_root.resolve(), compat_root=args.compat_root.resolve())
495+
results, excluded = _default_branch_results(all_results) if args.mainline_only else (all_results, [])
430496
report = _workspace_report(results, compat_root=args.compat_root.resolve())
497+
_with_workspace_scope(report, mainline_only=args.mainline_only, excluded=excluded)
431498
plan = _workspace_plan(report)
499+
plan["scope"] = report["scope"]
500+
plan["excluded_nondefault_checkouts"] = report["excluded_nondefault_checkouts"]
432501
if args.json:
433502
print(json.dumps(plan, ensure_ascii=False, indent=2))
434503
else:
@@ -500,12 +569,22 @@ def build_parser() -> argparse.ArgumentParser:
500569
report.add_argument("--projects-root", type=Path, default=DEFAULT_PROJECTS_ROOT)
501570
report.add_argument("--compat-root", type=Path, default=DEFAULT_COMPAT_ROOT)
502571
report.add_argument("--json", action="store_true")
572+
report.add_argument(
573+
"--mainline-only",
574+
action="store_true",
575+
help="Only include local checkouts on their configured origin default branch; never fetches or claims remote freshness.",
576+
)
503577
report.set_defaults(func=_cmd_report)
504578

505579
plan = subparsers.add_parser("plan", help="Render a ring-by-ring QSL convergence plan from current workspace state.")
506580
plan.add_argument("--projects-root", type=Path, default=DEFAULT_PROJECTS_ROOT)
507581
plan.add_argument("--compat-root", type=Path, default=DEFAULT_COMPAT_ROOT)
508582
plan.add_argument("--json", action="store_true")
583+
plan.add_argument(
584+
"--mainline-only",
585+
action="store_true",
586+
help="Only plan against local checkouts on their configured origin default branch; never fetches or claims remote freshness.",
587+
)
509588
plan.set_defaults(func=_cmd_plan)
510589

511590
matrix = subparsers.add_parser("generate-matrix", help="Generate or check the internal dependency matrix.")

python/tests/test_qslctl.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,49 @@ def test_plan_orders_rings_and_actions(self) -> None:
147147
self.assertEqual(payload["phases"][1]["warning_repositories"][0]["repo"], "WarningRepo")
148148
self.assertTrue(payload["phases"][0]["next_actions"][0].startswith("先清理 strict mismatch"))
149149

150+
def test_mainline_only_report_excludes_nondefault_local_checkouts(self) -> None:
151+
with tempfile.TemporaryDirectory() as workspace:
152+
root = Path(workspace)
153+
compat_root = root / "QuantRuntimeSettings"
154+
self._write_bundle(
155+
compat_root,
156+
"2026.07.2",
157+
{"QuantPlatformKit": "37c81901160c5b31127a27dba1c63944933fb6bf"},
158+
)
159+
self._write_repo_tiers(compat_root)
160+
self._write_repo(root / "MainRepo", "2026.07.2", "37c81901160c5b31127a27dba1c63944933fb6bf")
161+
self._write_repo(root / "FeatureRepo", "2026.07.2", "b" * 40)
162+
163+
def checkout_context(repo_root: Path) -> tuple[str | None, str | None]:
164+
return ("main", "main") if repo_root.name == "MainRepo" else ("agent/archived", "main")
165+
166+
buf = io.StringIO()
167+
with (
168+
patch.object(qslctl, "_is_quant_repo", return_value=True),
169+
patch.object(qslctl, "_checkout_context", side_effect=checkout_context),
170+
contextlib.redirect_stdout(buf),
171+
):
172+
exit_code = qslctl.main(
173+
[
174+
"report",
175+
"--projects-root",
176+
str(root),
177+
"--compat-root",
178+
str(compat_root),
179+
"--mainline-only",
180+
"--json",
181+
]
182+
)
183+
184+
payload = json.loads(buf.getvalue())
185+
self.assertEqual(exit_code, 0)
186+
self.assertEqual(payload["scope"], "local_default_branch_checkouts")
187+
self.assertEqual(payload["total_repositories"], 1)
188+
self.assertEqual(payload["strict_repositories"], 0)
189+
self.assertEqual(payload["excluded_nondefault_checkouts"], [
190+
{"repo": "FeatureRepo", "checkout_branch": "agent/archived", "default_branch": "main"}
191+
])
192+
150193
def test_generate_matrix_check_reports_stale_matrix(self) -> None:
151194
with tempfile.TemporaryDirectory() as workspace:
152195
root = Path(workspace)

0 commit comments

Comments
 (0)