@@ -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
6291def _is_quant_repo (repo_dir : Path ) -> bool :
@@ -86,6 +115,7 @@ def iter_qsl_repos(projects_root: Path) -> list[Path]:
86115
87116
88117def 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+
300357def _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
336393def _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
362425def _print_plan (plan : dict [str , Any ]) -> None :
@@ -416,8 +479,10 @@ def _cmd_check_all(args: argparse.Namespace) -> int:
416479
417480
418481def _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
428493def _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." )
0 commit comments