diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 87360d3ba..3607f5936 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -205,3 +205,41 @@ jobs: path: basic - name: Verify basic run: __test__/verify-basic.sh --archive + + test-git-container: + runs-on: ubuntu-latest + container: bitnami/git:latest + steps: + # Clone this repo + - name: Checkout + uses: actions/checkout@v3 + with: + path: v3 + + # Basic checkout using git + - name: Checkout basic + uses: ./v3 + with: + ref: test-data/v2/basic + - name: Verify basic + run: | + if [ ! -f "./basic-file.txt" ]; then + echo "Expected basic file does not exist" + exit 1 + fi + + # Verify .git folder + if [ ! -d "./.git" ]; then + echo "Expected ./.git folder to exist" + exit 1 + fi + + # Verify auth token + git config --global --add safe.directory "*" + git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main + + # needed to make checkout post cleanup succeed + - name: Fix Checkout v3 + uses: actions/checkout@v3 + with: + path: v3 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f40def82..530287003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## v2.4.1 +- [Set the safe directory option on git to prevent git commands failing when running in containers](https://github.com/actions/checkout/pull/762) + ## v2.3.1 - [Fix default branch resolution for .wiki and when using SSH](https://github.com/actions/checkout/pull/284) diff --git a/README.md b/README.md index 775cee592..dd09e13f6 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,11 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous # # Default: false submodules: '' + + # Add repository path as safe.directory for Git global config by running `git + # config --global --add safe.directory ` + # Default: true + set-safe-directory: '' ``` diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts index e14e948fb..a6731c2b4 100644 --- a/__test__/git-auth-helper.test.ts +++ b/__test__/git-auth-helper.test.ts @@ -643,10 +643,11 @@ describe('git-auth-helper tests', () => { expect(gitConfigContent.indexOf('http.')).toBeLessThan(0) }) - const removeGlobalAuth_removesOverride = 'removeGlobalAuth removes override' - it(removeGlobalAuth_removesOverride, async () => { + const removeGlobalConfig_removesOverride = + 'removeGlobalConfig removes override' + it(removeGlobalConfig_removesOverride, async () => { // Arrange - await setup(removeGlobalAuth_removesOverride) + await setup(removeGlobalConfig_removesOverride) const authHelper = gitAuthHelper.createAuthHelper(git, settings) await authHelper.configureAuth() await authHelper.configureGlobalAuth() @@ -655,7 +656,7 @@ describe('git-auth-helper tests', () => { await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig')) // Act - await authHelper.removeGlobalAuth() + await authHelper.removeGlobalConfig() // Assert expect(git.env['HOME']).toBeUndefined() @@ -776,7 +777,8 @@ async function setup(testName: string): Promise { sshKey: sshPath ? 'some ssh private key' : '', sshKnownHosts: '', sshStrict: true, - workflowOrganizationId: 123456 + workflowOrganizationId: 123456, + setSafeDirectory: true } } diff --git a/__test__/input-helper.test.ts b/__test__/input-helper.test.ts index a31b11cfa..1a8e5c96d 100644 --- a/__test__/input-helper.test.ts +++ b/__test__/input-helper.test.ts @@ -85,6 +85,7 @@ describe('input-helper tests', () => { expect(settings.repositoryName).toBe('some-repo') expect(settings.repositoryOwner).toBe('some-owner') expect(settings.repositoryPath).toBe(gitHubWorkspace) + expect(settings.setSafeDirectory).toBe(true) }) it('qualifies ref', async () => { diff --git a/action.yml b/action.yml index 91d39826c..96c535ef7 100644 --- a/action.yml +++ b/action.yml @@ -68,7 +68,10 @@ inputs: When the `ssh-key` input is not provided, SSH URLs beginning with `git@github.com:` are converted to HTTPS. default: false + set-safe-directory: + description: Add repository path as safe.directory for Git global config by running `git config --global --add safe.directory ` + default: true runs: - using: node12 + using: node16 main: dist/index.js post: dist/index.js diff --git a/dist/index.js b/dist/index.js index 8542f7dfc..94195a26b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3592,7 +3592,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.setSshKnownHostsPath = exports.setSshKeyPath = exports.setRepositoryPath = exports.SshKnownHostsPath = exports.SshKeyPath = exports.RepositoryPath = exports.IsPost = void 0; +exports.setSafeDirectory = exports.setSshKnownHostsPath = exports.setSshKeyPath = exports.setRepositoryPath = exports.SshKnownHostsPath = exports.SshKeyPath = exports.PostSetSafeDirectory = exports.RepositoryPath = exports.IsPost = void 0; const coreCommand = __importStar(__webpack_require__(431)); /** * Indicates whether the POST action is running @@ -3602,6 +3602,10 @@ exports.IsPost = !!process.env['STATE_isPost']; * The repository path for the POST action. The value is empty during the MAIN action. */ exports.RepositoryPath = process.env['STATE_repositoryPath'] || ''; +/** + * The set-safe-directory for the POST action. The value is set if input: 'safe-directory' is set during the MAIN action. + */ +exports.PostSetSafeDirectory = process.env['STATE_setSafeDirectory'] === 'true'; /** * The SSH key path for the POST action. The value is empty during the MAIN action. */ @@ -3631,6 +3635,13 @@ function setSshKnownHostsPath(sshKnownHostsPath) { coreCommand.issueCommand('save-state', { name: 'sshKnownHostsPath' }, sshKnownHostsPath); } exports.setSshKnownHostsPath = setSshKnownHostsPath; +/** + * Save the sef-safe-directory input so the POST action can retrieve the value. + */ +function setSafeDirectory() { + coreCommand.issueCommand('save-state', { name: 'setSafeDirectory' }, 'true'); +} +exports.setSafeDirectory = setSafeDirectory; // Publish a variable so that when the POST action runs, it can determine it should run the cleanup logic. // This is necessary since we don't have a separate entry point. if (!exports.IsPost) { @@ -6572,9 +6583,13 @@ class GitAuthHelper { yield this.configureToken(); }); } - configureGlobalAuth() { - var _a; + configureTempGlobalConfig() { + var _a, _b; return __awaiter(this, void 0, void 0, function* () { + // Already setup global config + if (((_a = this.temporaryHomePath) === null || _a === void 0 ? void 0 : _a.length) > 0) { + return path.join(this.temporaryHomePath, '.gitconfig'); + } // Create a temp home directory const runnerTemp = process.env['RUNNER_TEMP'] || ''; assert.ok(runnerTemp, 'RUNNER_TEMP is not defined'); @@ -6590,7 +6605,7 @@ class GitAuthHelper { configExists = true; } catch (err) { - if (((_a = err) === null || _a === void 0 ? void 0 : _a.code) !== 'ENOENT') { + if (((_b = err) === null || _b === void 0 ? void 0 : _b.code) !== 'ENOENT') { throw err; } } @@ -6601,10 +6616,17 @@ class GitAuthHelper { else { yield fs.promises.writeFile(newGitConfigPath, ''); } + // Override HOME + core.info(`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`); + this.git.setEnvironmentVariable('HOME', this.temporaryHomePath); + return newGitConfigPath; + }); + } + configureGlobalAuth() { + return __awaiter(this, void 0, void 0, function* () { + // 'configureTempGlobalConfig' noops if already set, just returns the path + const newGitConfigPath = yield this.configureTempGlobalConfig(); try { - // Override HOME - core.info(`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`); - this.git.setEnvironmentVariable('HOME', this.temporaryHomePath); // Configure the token yield this.configureToken(newGitConfigPath, true); // Configure HTTPS instead of SSH @@ -6657,11 +6679,14 @@ class GitAuthHelper { yield this.removeToken(); }); } - removeGlobalAuth() { + removeGlobalConfig() { + var _a; return __awaiter(this, void 0, void 0, function* () { - core.debug(`Unsetting HOME override`); - this.git.removeEnvironmentVariable('HOME'); - yield io.rmRF(this.temporaryHomePath); + if (((_a = this.temporaryHomePath) === null || _a === void 0 ? void 0 : _a.length) > 0) { + core.debug(`Unsetting HOME override`); + this.git.removeEnvironmentVariable('HOME'); + yield io.rmRF(this.temporaryHomePath); + } }); } configureSsh() { @@ -7326,40 +7351,59 @@ function getSource(settings) { core.startGroup('Getting Git version info'); const git = yield getGitCommandManager(settings); core.endGroup(); - // Prepare existing directory, otherwise recreate - if (isExisting) { - yield gitDirectoryHelper.prepareExistingDirectory(git, settings.repositoryPath, repositoryUrl, settings.clean, settings.ref); - } - if (!git) { - // Downloading using REST API - core.info(`The repository will be downloaded using the GitHub REST API`); - core.info(`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`); - if (settings.submodules) { - throw new Error(`Input 'submodules' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`); + let authHelper = null; + try { + if (git) { + authHelper = gitAuthHelper.createAuthHelper(git, settings); + if (settings.setSafeDirectory) { + // Setup the repository path as a safe directory, so if we pass this into a container job with a different user it doesn't fail + // Otherwise all git commands we run in a container fail + yield authHelper.configureTempGlobalConfig(); + core.info(`Adding repository directory to the temporary git global config as a safe directory`); + yield git + .config('safe.directory', settings.repositoryPath, true, true) + .catch(error => { + core.info(`Failed to initialize safe directory with error: ${error}`); + }); + stateHelper.setSafeDirectory(); + } } - else if (settings.sshKey) { - throw new Error(`Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`); + // Prepare existing directory, otherwise recreate + if (isExisting) { + yield gitDirectoryHelper.prepareExistingDirectory(git, settings.repositoryPath, repositoryUrl, settings.clean, settings.ref); + } + if (!git) { + // Downloading using REST API + core.info(`The repository will be downloaded using the GitHub REST API`); + core.info(`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`); + if (settings.submodules) { + throw new Error(`Input 'submodules' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`); + } + else if (settings.sshKey) { + throw new Error(`Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`); + } + yield githubApiHelper.downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath); + return; + } + // Save state for POST action + stateHelper.setRepositoryPath(settings.repositoryPath); + // Initialize the repository + if (!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))) { + core.startGroup('Initializing the repository'); + yield git.init(); + yield git.remoteAdd('origin', repositoryUrl); + core.endGroup(); + } + // Disable automatic garbage collection + core.startGroup('Disabling automatic garbage collection'); + if (!(yield git.tryDisableAutomaticGarbageCollection())) { + core.warning(`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`); } - yield githubApiHelper.downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath); - return; - } - // Save state for POST action - stateHelper.setRepositoryPath(settings.repositoryPath); - // Initialize the repository - if (!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))) { - core.startGroup('Initializing the repository'); - yield git.init(); - yield git.remoteAdd('origin', repositoryUrl); core.endGroup(); - } - // Disable automatic garbage collection - core.startGroup('Disabling automatic garbage collection'); - if (!(yield git.tryDisableAutomaticGarbageCollection())) { - core.warning(`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`); - } - core.endGroup(); - const authHelper = gitAuthHelper.createAuthHelper(git, settings); - try { + // If we didn't initialize it above, do it now + if (!authHelper) { + authHelper = gitAuthHelper.createAuthHelper(git, settings); + } // Configure auth core.startGroup('Setting up auth'); yield authHelper.configureAuth(); @@ -7415,27 +7459,21 @@ function getSource(settings) { core.endGroup(); // Submodules if (settings.submodules) { - try { - // Temporarily override global config - core.startGroup('Setting up auth for fetching submodules'); - yield authHelper.configureGlobalAuth(); - core.endGroup(); - // Checkout submodules - core.startGroup('Fetching submodules'); - yield git.submoduleSync(settings.nestedSubmodules); - yield git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules); - yield git.submoduleForeach('git config --local gc.auto 0', settings.nestedSubmodules); + // Temporarily override global config + core.startGroup('Setting up auth for fetching submodules'); + yield authHelper.configureGlobalAuth(); + core.endGroup(); + // Checkout submodules + core.startGroup('Fetching submodules'); + yield git.submoduleSync(settings.nestedSubmodules); + yield git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules); + yield git.submoduleForeach('git config --local gc.auto 0', settings.nestedSubmodules); + core.endGroup(); + // Persist credentials + if (settings.persistCredentials) { + core.startGroup('Persisting credentials for submodules'); + yield authHelper.configureSubmoduleAuth(); core.endGroup(); - // Persist credentials - if (settings.persistCredentials) { - core.startGroup('Persisting credentials for submodules'); - yield authHelper.configureSubmoduleAuth(); - core.endGroup(); - } - } - finally { - // Remove temporary global config override - yield authHelper.removeGlobalAuth(); } } // Get commit information @@ -7447,10 +7485,13 @@ function getSource(settings) { } finally { // Remove auth - if (!settings.persistCredentials) { - core.startGroup('Removing auth'); - yield authHelper.removeAuth(); - core.endGroup(); + if (authHelper) { + if (!settings.persistCredentials) { + core.startGroup('Removing auth'); + yield authHelper.removeAuth(); + core.endGroup(); + } + authHelper.removeGlobalConfig(); } } }); @@ -7472,7 +7513,23 @@ function cleanup(repositoryPath) { } // Remove auth const authHelper = gitAuthHelper.createAuthHelper(git); - yield authHelper.removeAuth(); + try { + if (stateHelper.PostSetSafeDirectory) { + // Setup the repository path as a safe directory, so if we pass this into a container job with a different user it doesn't fail + // Otherwise all git commands we run in a container fail + yield authHelper.configureTempGlobalConfig(); + core.info(`Adding repository directory to the temporary git global config as a safe directory`); + yield git + .config('safe.directory', repositoryPath, true, true) + .catch(error => { + core.info(`Failed to initialize safe directory with error: ${error}`); + }); + } + yield authHelper.removeAuth(); + } + finally { + yield authHelper.removeGlobalConfig(); + } }); } exports.cleanup = cleanup; @@ -17244,6 +17301,9 @@ function getInputs() { (core.getInput('persist-credentials') || 'false').toUpperCase() === 'TRUE'; // Workflow organization ID result.workflowOrganizationId = yield workflowContextHelper.getOrganizationId(); + // Set safe.directory in git global config. + result.setSafeDirectory = + (core.getInput('set-safe-directory') || 'true').toUpperCase() === 'TRUE'; return result; }); } diff --git a/src/git-auth-helper.ts b/src/git-auth-helper.ts index 233b3e66a..8a1c7c379 100644 --- a/src/git-auth-helper.ts +++ b/src/git-auth-helper.ts @@ -19,8 +19,9 @@ export interface IGitAuthHelper { configureAuth(): Promise configureGlobalAuth(): Promise configureSubmoduleAuth(): Promise + configureTempGlobalConfig(): Promise removeAuth(): Promise - removeGlobalAuth(): Promise + removeGlobalConfig(): Promise } export function createAuthHelper( @@ -80,7 +81,11 @@ class GitAuthHelper { await this.configureToken() } - async configureGlobalAuth(): Promise { + async configureTempGlobalConfig(): Promise { + // Already setup global config + if (this.temporaryHomePath?.length > 0) { + return path.join(this.temporaryHomePath, '.gitconfig') + } // Create a temp home directory const runnerTemp = process.env['RUNNER_TEMP'] || '' assert.ok(runnerTemp, 'RUNNER_TEMP is not defined') @@ -110,13 +115,19 @@ class GitAuthHelper { await fs.promises.writeFile(newGitConfigPath, '') } - try { - // Override HOME - core.info( - `Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes` - ) - this.git.setEnvironmentVariable('HOME', this.temporaryHomePath) + // Override HOME + core.info( + `Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes` + ) + this.git.setEnvironmentVariable('HOME', this.temporaryHomePath) + return newGitConfigPath + } + + async configureGlobalAuth(): Promise { + // 'configureTempGlobalConfig' noops if already set, just returns the path + const newGitConfigPath = await this.configureTempGlobalConfig() + try { // Configure the token await this.configureToken(newGitConfigPath, true) @@ -181,10 +192,12 @@ class GitAuthHelper { await this.removeToken() } - async removeGlobalAuth(): Promise { - core.debug(`Unsetting HOME override`) - this.git.removeEnvironmentVariable('HOME') - await io.rmRF(this.temporaryHomePath) + async removeGlobalConfig(): Promise { + if (this.temporaryHomePath?.length > 0) { + core.debug(`Unsetting HOME override`) + this.git.removeEnvironmentVariable('HOME') + await io.rmRF(this.temporaryHomePath) + } } private async configureSsh(): Promise { diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts index 42a12e04e..545a7a3bd 100644 --- a/src/git-source-provider.ts +++ b/src/git-source-provider.ts @@ -36,68 +36,94 @@ export async function getSource(settings: IGitSourceSettings): Promise { const git = await getGitCommandManager(settings) core.endGroup() - // Prepare existing directory, otherwise recreate - if (isExisting) { - await gitDirectoryHelper.prepareExistingDirectory( - git, - settings.repositoryPath, - repositoryUrl, - settings.clean, - settings.ref - ) - } + let authHelper: gitAuthHelper.IGitAuthHelper | null = null + try { + if (git) { + authHelper = gitAuthHelper.createAuthHelper(git, settings) + if (settings.setSafeDirectory) { + // Setup the repository path as a safe directory, so if we pass this into a container job with a different user it doesn't fail + // Otherwise all git commands we run in a container fail + await authHelper.configureTempGlobalConfig() + core.info( + `Adding repository directory to the temporary git global config as a safe directory` + ) - if (!git) { - // Downloading using REST API - core.info(`The repository will be downloaded using the GitHub REST API`) - core.info( - `To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH` - ) - if (settings.submodules) { - throw new Error( - `Input 'submodules' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.` + await git + .config('safe.directory', settings.repositoryPath, true, true) + .catch(error => { + core.info( + `Failed to initialize safe directory with error: ${error}` + ) + }) + + stateHelper.setSafeDirectory() + } + } + + // Prepare existing directory, otherwise recreate + if (isExisting) { + await gitDirectoryHelper.prepareExistingDirectory( + git, + settings.repositoryPath, + repositoryUrl, + settings.clean, + settings.ref ) - } else if (settings.sshKey) { - throw new Error( - `Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.` + } + + if (!git) { + // Downloading using REST API + core.info(`The repository will be downloaded using the GitHub REST API`) + core.info( + `To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH` ) + if (settings.submodules) { + throw new Error( + `Input 'submodules' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.` + ) + } else if (settings.sshKey) { + throw new Error( + `Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.` + ) + } + + await githubApiHelper.downloadRepository( + settings.authToken, + settings.repositoryOwner, + settings.repositoryName, + settings.ref, + settings.commit, + settings.repositoryPath + ) + return } - await githubApiHelper.downloadRepository( - settings.authToken, - settings.repositoryOwner, - settings.repositoryName, - settings.ref, - settings.commit, - settings.repositoryPath - ) - return - } + // Save state for POST action + stateHelper.setRepositoryPath(settings.repositoryPath) - // Save state for POST action - stateHelper.setRepositoryPath(settings.repositoryPath) + // Initialize the repository + if ( + !fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git')) + ) { + core.startGroup('Initializing the repository') + await git.init() + await git.remoteAdd('origin', repositoryUrl) + core.endGroup() + } - // Initialize the repository - if ( - !fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git')) - ) { - core.startGroup('Initializing the repository') - await git.init() - await git.remoteAdd('origin', repositoryUrl) + // Disable automatic garbage collection + core.startGroup('Disabling automatic garbage collection') + if (!(await git.tryDisableAutomaticGarbageCollection())) { + core.warning( + `Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.` + ) + } core.endGroup() - } - // Disable automatic garbage collection - core.startGroup('Disabling automatic garbage collection') - if (!(await git.tryDisableAutomaticGarbageCollection())) { - core.warning( - `Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.` - ) - } - core.endGroup() - - const authHelper = gitAuthHelper.createAuthHelper(git, settings) - try { + // If we didn't initialize it above, do it now + if (!authHelper) { + authHelper = gitAuthHelper.createAuthHelper(git, settings) + } // Configure auth core.startGroup('Setting up auth') await authHelper.configureAuth() @@ -170,34 +196,26 @@ export async function getSource(settings: IGitSourceSettings): Promise { // Submodules if (settings.submodules) { - try { - // Temporarily override global config - core.startGroup('Setting up auth for fetching submodules') - await authHelper.configureGlobalAuth() - core.endGroup() + // Temporarily override global config + core.startGroup('Setting up auth for fetching submodules') + await authHelper.configureGlobalAuth() + core.endGroup() - // Checkout submodules - core.startGroup('Fetching submodules') - await git.submoduleSync(settings.nestedSubmodules) - await git.submoduleUpdate( - settings.fetchDepth, - settings.nestedSubmodules - ) - await git.submoduleForeach( - 'git config --local gc.auto 0', - settings.nestedSubmodules - ) - core.endGroup() + // Checkout submodules + core.startGroup('Fetching submodules') + await git.submoduleSync(settings.nestedSubmodules) + await git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules) + await git.submoduleForeach( + 'git config --local gc.auto 0', + settings.nestedSubmodules + ) + core.endGroup() - // Persist credentials - if (settings.persistCredentials) { - core.startGroup('Persisting credentials for submodules') - await authHelper.configureSubmoduleAuth() - core.endGroup() - } - } finally { - // Remove temporary global config override - await authHelper.removeGlobalAuth() + // Persist credentials + if (settings.persistCredentials) { + core.startGroup('Persisting credentials for submodules') + await authHelper.configureSubmoduleAuth() + core.endGroup() } } @@ -218,10 +236,13 @@ export async function getSource(settings: IGitSourceSettings): Promise { ) } finally { // Remove auth - if (!settings.persistCredentials) { - core.startGroup('Removing auth') - await authHelper.removeAuth() - core.endGroup() + if (authHelper) { + if (!settings.persistCredentials) { + core.startGroup('Removing auth') + await authHelper.removeAuth() + core.endGroup() + } + authHelper.removeGlobalConfig() } } } @@ -244,7 +265,26 @@ export async function cleanup(repositoryPath: string): Promise { // Remove auth const authHelper = gitAuthHelper.createAuthHelper(git) - await authHelper.removeAuth() + try { + if (stateHelper.PostSetSafeDirectory) { + // Setup the repository path as a safe directory, so if we pass this into a container job with a different user it doesn't fail + // Otherwise all git commands we run in a container fail + await authHelper.configureTempGlobalConfig() + core.info( + `Adding repository directory to the temporary git global config as a safe directory` + ) + + await git + .config('safe.directory', repositoryPath, true, true) + .catch(error => { + core.info(`Failed to initialize safe directory with error: ${error}`) + }) + } + + await authHelper.removeAuth() + } finally { + await authHelper.removeGlobalConfig() + } } async function getGitCommandManager( diff --git a/src/git-source-settings.ts b/src/git-source-settings.ts index 19f46513d..6fa3960fd 100644 --- a/src/git-source-settings.ts +++ b/src/git-source-settings.ts @@ -78,4 +78,9 @@ export interface IGitSourceSettings { * Organization ID for the currently running workflow (used for auth settings) */ workflowOrganizationId: number | undefined + + /** + * Indicates whether to add repositoryPath as safe.directory in git global config + */ + setSafeDirectory: boolean } diff --git a/src/input-helper.ts b/src/input-helper.ts index 40e6de44e..8c2f90157 100644 --- a/src/input-helper.ts +++ b/src/input-helper.ts @@ -122,5 +122,8 @@ export async function getInputs(): Promise { // Workflow organization ID result.workflowOrganizationId = await workflowContextHelper.getOrganizationId() + // Set safe.directory in git global config. + result.setSafeDirectory = + (core.getInput('set-safe-directory') || 'true').toUpperCase() === 'TRUE' return result } diff --git a/src/state-helper.ts b/src/state-helper.ts index 3c657b1dd..2db79f9d4 100644 --- a/src/state-helper.ts +++ b/src/state-helper.ts @@ -11,6 +11,12 @@ export const IsPost = !!process.env['STATE_isPost'] export const RepositoryPath = (process.env['STATE_repositoryPath'] as string) || '' +/** + * The set-safe-directory for the POST action. The value is set if input: 'safe-directory' is set during the MAIN action. + */ +export const PostSetSafeDirectory = + (process.env['STATE_setSafeDirectory'] as string) === 'true' + /** * The SSH key path for the POST action. The value is empty during the MAIN action. */ @@ -51,6 +57,13 @@ export function setSshKnownHostsPath(sshKnownHostsPath: string) { ) } +/** + * Save the sef-safe-directory input so the POST action can retrieve the value. + */ +export function setSafeDirectory() { + coreCommand.issueCommand('save-state', {name: 'setSafeDirectory'}, 'true') +} + // Publish a variable so that when the POST action runs, it can determine it should run the cleanup logic. // This is necessary since we don't have a separate entry point. if (!IsPost) {