@@ -564,6 +564,97 @@ def get_fbsource_repo_data(build_options) -> FbsourceRepoData:
564564 return cached_data
565565
566566
567+ def is_public_commit (build_options ) -> bool : # noqa: C901
568+ """Check if the current commit is public (shipped/will be shipped to remote).
569+
570+ Works across git, sapling (sl), and hg repositories:
571+ - For hg/sapling: Uses 'phase' command to check if commit is public
572+ - For git: Checks if commit exists in remote branches
573+
574+ Returns True if public, False if draft/local-only or on error (conservative).
575+ """
576+ # Use fbsource_dir if available (Meta internal), otherwise fall back to repo_root
577+ repo_dir = build_options .fbsource_dir or build_options .repo_root
578+ if not repo_dir :
579+ # No repository detected, be conservative
580+ return False
581+
582+ env = Env ()
583+ env .set ("HGPLAIN" , "1" )
584+ env_dict = dict (env .items ())
585+
586+ try :
587+ # Try hg/sapling phase command first (works for both hg and sl)
588+ # Try 'sl' first as it's the preferred tool at Meta
589+ for cmd in [["sl" , "phase" , "-r" , "." ], ["hg" , "phase" , "-r" , "." ]]:
590+ try :
591+ output = (
592+ subprocess .check_output (
593+ cmd , cwd = repo_dir , env = env_dict , stderr = subprocess .DEVNULL
594+ )
595+ .decode ("ascii" )
596+ .strip ()
597+ )
598+ # Output format: "hash: public" or "hash: draft"
599+ return "public" in output
600+ except (subprocess .CalledProcessError , FileNotFoundError ):
601+ continue
602+
603+ # Try git if hg/sl didn't work
604+ try :
605+ # Detect the default branch for origin remote
606+ default_branch = None
607+ try :
608+ # Get the symbolic ref for origin/HEAD to find default branch
609+ output = (
610+ subprocess .check_output (
611+ ["git" , "symbolic-ref" , "refs/remotes/origin/HEAD" ],
612+ cwd = repo_dir ,
613+ stderr = subprocess .DEVNULL ,
614+ )
615+ .decode ("ascii" )
616+ .strip ()
617+ )
618+ # Output format: "refs/remotes/origin/main"
619+ if output .startswith ("refs/remotes/" ):
620+ default_branch = output
621+ except subprocess .CalledProcessError :
622+ # If symbolic-ref fails, fall back to common names
623+ pass
624+
625+ # Build list of branches to check
626+ branches_to_check = []
627+ if default_branch :
628+ branches_to_check .append (default_branch )
629+ # Also try common defaults as fallback
630+ branches_to_check .extend (["origin/main" , "origin/master" ])
631+
632+ # Check if HEAD is an ancestor of any of these branches
633+ for branch in branches_to_check :
634+ try :
635+ subprocess .check_output (
636+ ["git" , "merge-base" , "--is-ancestor" , "HEAD" , branch ],
637+ cwd = repo_dir ,
638+ stderr = subprocess .DEVNULL ,
639+ )
640+ # If command succeeds (exit 0), HEAD is an ancestor of the branch
641+ return True
642+ except subprocess .CalledProcessError :
643+ # Not an ancestor of this branch, try next
644+ continue
645+ # HEAD is not in any default branch
646+ return False
647+ except FileNotFoundError :
648+ pass
649+
650+ # If all VCS commands failed, be conservative and don't upload
651+ return False
652+
653+ except Exception :
654+ # On any unexpected error, be conservative and don't upload
655+ return False
656+
657+
567658class SimpleShipitTransformerFetcher (Fetcher ):
568659 def __init__ (self , build_options , manifest , ctx ) -> None :
569660 self .build_options = build_options
0 commit comments