@@ -17,6 +17,7 @@ const { checkFileProtection } = require("./manifest_file_helpers.cjs");
1717const { buildWorkflowRunUrl } = require ( "./workflow_metadata_helpers.cjs" ) ;
1818const { renderTemplateFromFile, buildProtectedFileList } = require ( "./messages_core.cjs" ) ;
1919const { getGitAuthEnv } = require ( "./git_helpers.cjs" ) ;
20+ const { findRepoCheckout } = require ( "./find_repo_checkout.cjs" ) ;
2021
2122/**
2223 * @typedef {import('./types/handler-factory').HandlerFactoryFunction } HandlerFactoryFunction
@@ -353,7 +354,27 @@ async function main(config = {}) {
353354
354355 core . info ( `Target repository: ${ itemRepo } ` ) ;
355356
356- // Fetch the specific PR to get its head branch, title, and labels
357+ // Resolve the checkout directory for the target repo.
358+ // When the target repo differs from the workflow repo, it may be checked out
359+ // into a subdirectory of GITHUB_WORKSPACE (e.g. via actions/checkout path:).
360+ // All git operations must run from that directory, not from GITHUB_WORKSPACE.
361+ let repoCwd = undefined ;
362+ const workflowRepo = process . env . GITHUB_REPOSITORY || "" ;
363+ if ( itemRepo . toLowerCase ( ) !== workflowRepo . toLowerCase ( ) ) {
364+ core . info ( `Cross-repo push: looking for checkout of ${ itemRepo } ` ) ;
365+ const checkoutResult = findRepoCheckout ( itemRepo , process . env . GITHUB_WORKSPACE , { allowedRepos } ) ;
366+ if ( ! checkoutResult . success ) {
367+ return {
368+ success : false ,
369+ error : `Repository '${ itemRepo } ' not found in workspace. Check out the target repo with actions/checkout and set its 'path' input so the checkout can be located. If checking out multiple repositories, ensure each actions/checkout step uses the appropriate 'path' input.` ,
370+ } ;
371+ }
372+ repoCwd = checkoutResult . path ;
373+ core . info ( `Found checkout for ${ itemRepo } at: ${ repoCwd } ` ) ;
374+ }
375+
376+ // Base options for all git exec calls - includes cwd when running in a subdirectory checkout
377+ const baseGitOpts = repoCwd ? { cwd : repoCwd } : { } ;
357378 let pullRequest ;
358379 try {
359380 const response = await githubClient . rest . pulls . get ( {
@@ -490,6 +511,7 @@ async function main(config = {}) {
490511 {
491512 const lsRemoteResult = await exec . getExecOutput ( "git" , [ "ls-remote" , "--exit-code" , "--heads" , "origin" , branchName ] , {
492513 env : { ...process . env , ...gitAuthEnv } ,
514+ ...baseGitOpts ,
493515 ignoreReturnCode : true ,
494516 } ) ;
495517
@@ -525,6 +547,7 @@ async function main(config = {}) {
525547 core . info ( `Fetching branch: ${ branchName } ` ) ;
526548 await exec . exec ( "git" , [ "fetch" , "origin" , `${ branchName } :refs/remotes/origin/${ branchName } ` ] , {
527549 env : { ...process . env , ...gitAuthEnv } ,
550+ ...baseGitOpts ,
528551 } ) ;
529552 } catch ( fetchError ) {
530553 const fetchErrorMessage = fetchError instanceof Error ? fetchError . message : String ( fetchError ) ;
@@ -538,7 +561,7 @@ async function main(config = {}) {
538561
539562 // Check if branch exists on origin
540563 try {
541- await exec . exec ( `git rev-parse --verify origin/${ branchName } ` ) ;
564+ await exec . exec ( `git rev-parse --verify origin/${ branchName } ` , [ ] , baseGitOpts ) ;
542565 } catch ( verifyError ) {
543566 const missingBranchError = MISSING_BRANCH_ERROR_TEMPLATE ( branchName ) ;
544567 if ( ignoreMissingBranchFailure ) {
@@ -550,7 +573,7 @@ async function main(config = {}) {
550573
551574 // Checkout the branch from origin
552575 try {
553- await exec . exec ( `git checkout -B ${ branchName } origin/${ branchName } ` ) ;
576+ await exec . exec ( `git checkout -B ${ branchName } origin/${ branchName } ` , [ ] , baseGitOpts ) ;
554577 core . info ( `Checked out existing branch from origin: ${ branchName } ` ) ;
555578 } catch ( checkoutError ) {
556579 return { success : false , error : `Failed to checkout branch ${ branchName } : ${ checkoutError instanceof Error ? checkoutError . message : String ( checkoutError ) } ` } ;
@@ -566,7 +589,7 @@ async function main(config = {}) {
566589 if ( hasChanges ) {
567590 // Capture HEAD before applying changes to compute new-commit count later
568591 try {
569- const { stdout } = await exec . getExecOutput ( "git" , [ "rev-parse" , "HEAD" ] ) ;
592+ const { stdout } = await exec . getExecOutput ( "git" , [ "rev-parse" , "HEAD" ] , baseGitOpts ) ;
570593 remoteHeadBeforePatch = stdout . trim ( ) ;
571594 } catch {
572595 // Non-fatal - extra empty commit will be skipped
@@ -579,24 +602,24 @@ async function main(config = {}) {
579602 const bundleRef = `refs/bundles/push-${ branchName . replace ( / [ ^ a - z A - Z 0 - 9 - ] / g, "-" ) } ` ;
580603 try {
581604 // Fetch from bundle into a temporary ref
582- await exec . exec ( "git" , [ "fetch" , bundleFilePath , `refs/heads/${ message . branch } :${ bundleRef } ` ] ) ;
605+ await exec . exec ( "git" , [ "fetch" , bundleFilePath , `refs/heads/${ message . branch } :${ bundleRef } ` ] , baseGitOpts ) ;
583606 core . info ( `Fetched bundle to ${ bundleRef } ` ) ;
584607
585608 // Fast-forward the current branch to the bundle tip
586- await exec . exec ( "git" , [ "merge" , "--ff-only" , bundleRef ] ) ;
609+ await exec . exec ( "git" , [ "merge" , "--ff-only" , bundleRef ] , baseGitOpts ) ;
587610 core . info ( "Fast-forwarded branch to bundle tip" ) ;
588611
589612 // Clean up the temporary ref
590613 try {
591- await exec . exec ( "git" , [ "update-ref" , "-d" , bundleRef ] ) ;
614+ await exec . exec ( "git" , [ "update-ref" , "-d" , bundleRef ] , baseGitOpts ) ;
592615 } catch {
593616 // Non-fatal cleanup
594617 }
595618 } catch ( bundleError ) {
596619 core . error ( `Failed to apply bundle: ${ bundleError instanceof Error ? bundleError . message : String ( bundleError ) } ` ) ;
597620 // Clean up temp ref if it exists
598621 try {
599- await exec . exec ( "git" , [ "update-ref" , "-d" , bundleRef ] ) ;
622+ await exec . exec ( "git" , [ "update-ref" , "-d" , bundleRef ] , baseGitOpts ) ;
600623 } catch {
601624 // Ignore
602625 }
@@ -631,7 +654,7 @@ async function main(config = {}) {
631654
632655 // Use --3way to handle cross-repo patches where the patch base may differ from target repo
633656 // This allows git to resolve create-vs-modify mismatches when a file exists in target but not source
634- await exec . exec ( `git am --3way ${ patchFilePath } ` ) ;
657+ await exec . exec ( `git am --3way ${ patchFilePath } ` , [ ] , baseGitOpts ) ;
635658 core . info ( "Patch applied successfully" ) ;
636659 } catch ( error ) {
637660 core . error ( `Failed to apply patch: ${ getErrorMessage ( error ) } ` ) ;
@@ -640,23 +663,23 @@ async function main(config = {}) {
640663 try {
641664 core . info ( "Investigating patch failure..." ) ;
642665
643- const statusResult = await exec . getExecOutput ( "git" , [ "status" ] ) ;
666+ const statusResult = await exec . getExecOutput ( "git" , [ "status" ] , baseGitOpts ) ;
644667 core . info ( "Git status output:" ) ;
645668 core . info ( statusResult . stdout ) ;
646669
647- const logResult = await exec . getExecOutput ( "git" , [ "log" , "--oneline" , "-5" ] ) ;
670+ const logResult = await exec . getExecOutput ( "git" , [ "log" , "--oneline" , "-5" ] , baseGitOpts ) ;
648671 core . info ( "Recent commits (last 5):" ) ;
649672 core . info ( logResult . stdout ) ;
650673
651- const diffResult = await exec . getExecOutput ( "git" , [ "diff" , "HEAD" ] ) ;
674+ const diffResult = await exec . getExecOutput ( "git" , [ "diff" , "HEAD" ] , baseGitOpts ) ;
652675 core . info ( "Uncommitted changes:" ) ;
653676 core . info ( diffResult . stdout && diffResult . stdout . trim ( ) ? diffResult . stdout : "(no uncommitted changes)" ) ;
654677
655- const patchDiffResult = await exec . getExecOutput ( "git" , [ "am" , "--show-current-patch=diff" ] ) ;
678+ const patchDiffResult = await exec . getExecOutput ( "git" , [ "am" , "--show-current-patch=diff" ] , baseGitOpts ) ;
656679 core . info ( "Failed patch diff:" ) ;
657680 core . info ( patchDiffResult . stdout ) ;
658681
659- const patchFullResult = await exec . getExecOutput ( "git" , [ "am" , "--show-current-patch" ] ) ;
682+ const patchFullResult = await exec . getExecOutput ( "git" , [ "am" , "--show-current-patch" ] , baseGitOpts ) ;
660683 core . info ( "Failed patch (full):" ) ;
661684 core . info ( patchFullResult . stdout ) ;
662685 } catch ( investigateError ) {
@@ -679,12 +702,13 @@ async function main(config = {}) {
679702 const reviewBranchName = normalizeBranchName ( `${ branchName } -review` , String ( Date . now ( ) ) ) ;
680703 try {
681704 // Rename current local branch to review branch
682- await exec . exec ( "git" , [ "checkout" , "-b" , reviewBranchName ] ) ;
705+ await exec . exec ( "git" , [ "checkout" , "-b" , reviewBranchName ] , baseGitOpts ) ;
683706 core . info ( `Created review branch: ${ reviewBranchName } ` ) ;
684707
685708 // Push the review branch
686709 await exec . exec ( "git" , [ "push" , "origin" , reviewBranchName ] , {
687710 env : { ...process . env , ...gitAuthEnv } ,
711+ ...baseGitOpts ,
688712 } ) ;
689713 core . info ( `Pushed review branch: ${ reviewBranchName } ` ) ;
690714
@@ -750,7 +774,7 @@ async function main(config = {}) {
750774 repo : repoParts . repo ,
751775 branch : branchName ,
752776 baseRef : remoteHeadBeforePatch || `origin/${ branchName } ` ,
753- cwd : process . cwd ( ) ,
777+ ... baseGitOpts ,
754778 gitAuthEnv,
755779 } ) ;
756780 if ( pushedSha ) {
@@ -771,6 +795,7 @@ async function main(config = {}) {
771795 try {
772796 const lsRemoteAfterPushResult = await exec . getExecOutput ( "git" , [ "ls-remote" , "--exit-code" , "--heads" , "origin" , branchName ] , {
773797 env : { ...process . env , ...gitAuthEnv } ,
798+ ...baseGitOpts ,
774799 ignoreReturnCode : true ,
775800 } ) ;
776801
@@ -790,9 +815,10 @@ async function main(config = {}) {
790815 const fallbackBranchName = normalizeBranchName ( `${ branchName } -fallback` , String ( Date . now ( ) ) ) ;
791816 core . warning ( `Non-fast-forward push detected; creating fallback pull request from '${ fallbackBranchName } ' to '${ branchName } '` ) ;
792817 try {
793- await exec . exec ( "git" , [ "checkout" , "-b" , fallbackBranchName ] ) ;
818+ await exec . exec ( "git" , [ "checkout" , "-b" , fallbackBranchName ] , baseGitOpts ) ;
794819 await exec . exec ( "git" , [ "push" , "origin" , fallbackBranchName ] , {
795820 env : { ...process . env , ...gitAuthEnv } ,
821+ ...baseGitOpts ,
796822 } ) ;
797823
798824 const fallbackBody = [
@@ -842,7 +868,7 @@ async function main(config = {}) {
842868 // Count new commits pushed for the CI trigger decision
843869 if ( remoteHeadBeforePatch ) {
844870 try {
845- const { stdout : countStr } = await exec . getExecOutput ( "git" , [ "rev-list" , "--count" , `${ remoteHeadBeforePatch } ..HEAD` ] ) ;
871+ const { stdout : countStr } = await exec . getExecOutput ( "git" , [ "rev-list" , "--count" , `${ remoteHeadBeforePatch } ..HEAD` ] , baseGitOpts ) ;
846872 newCommitCount = parseInt ( countStr . trim ( ) , 10 ) ;
847873 core . info ( `${ newCommitCount } new commit(s) pushed to branch` ) ;
848874 } catch {
@@ -872,7 +898,7 @@ async function main(config = {}) {
872898 // Fall back to local HEAD only if the helper did not return one.
873899 let commitSha = pushedCommitSha ;
874900 if ( ! commitSha ) {
875- const commitShaRes = await exec . getExecOutput ( "git" , [ "rev-parse" , "HEAD" ] ) ;
901+ const commitShaRes = await exec . getExecOutput ( "git" , [ "rev-parse" , "HEAD" ] , baseGitOpts ) ;
876902 if ( commitShaRes . exitCode !== 0 ) {
877903 return { success : false , error : "Failed to get commit SHA" } ;
878904 }
0 commit comments