This commit is contained in:
Camilo Celis Guzman 2026-09-23 22:25:51 +09:00 committed by GitHub
commit e784eb15a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 199 additions and 82 deletions

View File

@ -32,6 +32,8 @@ Only a single commit is fetched by default, for the ref/SHA that triggered the w
The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set `persist-credentials: false` to opt-out. The auth token is persisted in the local git config. This enables your scripts to run authenticated git commands. The token is removed during post-job cleanup. Set `persist-credentials: false` to opt-out.
For token-authenticated submodules, checkout keeps its SSH-to-HTTPS URL rewrites in a temporary config alongside the submodule credentials. Both are removed during credential cleanup, without changing pre-existing URL rewrite rules. Unmarked local rewrites left by older checkout versions are not removed automatically because they cannot be distinguished from user configuration.
When Git 2.18 or higher is not in your PATH, falls back to the REST API to download the files. When Git 2.18 or higher is not in your PATH, falls back to the REST API to download the files.
### Note ### Note

View File

@ -30,6 +30,8 @@ jest.unstable_mockModule('@actions/core', () => ({
jest.unstable_mockModule('../src/state-helper.js', () => ({ jest.unstable_mockModule('../src/state-helper.js', () => ({
setSshKeyPath: jest.fn(), setSshKeyPath: jest.fn(),
setSshKnownHostsPath: jest.fn(), setSshKnownHostsPath: jest.fn(),
setCredentialsConfigPaths: jest.fn(),
CredentialsConfigPaths: [],
IsPost: false, IsPost: false,
RepositoryPath: '' RepositoryPath: ''
})) }))
@ -37,6 +39,7 @@ jest.unstable_mockModule('../src/state-helper.js', () => ({
// Dynamic imports after mocking // Dynamic imports after mocking
const core = await import('@actions/core') const core = await import('@actions/core')
const gitAuthHelper = await import('../src/git-auth-helper.js') const gitAuthHelper = await import('../src/git-auth-helper.js')
const stateHelper = await import('../src/state-helper.js')
type IGitCommandManager = type IGitCommandManager =
import('../src/git-command-manager.js').IGitCommandManager import('../src/git-command-manager.js').IGitCommandManager
type IGitSourceSettings = type IGitSourceSettings =
@ -67,6 +70,7 @@ describe('git-auth-helper tests', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks() jest.clearAllMocks()
stateHelper.CredentialsConfigPaths.length = 0
}) })
afterEach(() => { afterEach(() => {
@ -557,10 +561,7 @@ describe('git-auth-helper tests', () => {
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
// Assert // Assert
expect(mockSubmoduleForeach).toBeCalledTimes(1) expect(mockSubmoduleForeach).not.toHaveBeenCalled()
expect(mockSubmoduleForeach.mock.calls[0][0] as string).toMatch(
/unset-all.*insteadOf/
)
} }
) )
@ -590,10 +591,7 @@ describe('git-auth-helper tests', () => {
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
// Assert // Assert
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(1) expect(mockSubmoduleForeach).not.toHaveBeenCalled()
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/
)
} }
) )
@ -616,16 +614,21 @@ describe('git-auth-helper tests', () => {
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
// Assert // Assert
// Should configure insteadOf (2 calls for two values) expect(mockSubmoduleForeach).not.toHaveBeenCalled()
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3) const credentialsFiles = await fs.promises.readdir(runnerTemp)
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( const contents = await Promise.all(
/unset-all.*insteadOf/ credentialsFiles.map(file =>
fs.promises.readFile(path.join(runnerTemp, file), 'utf8')
) )
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(
/url.*insteadOf.*git@github.com:/
) )
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch( const submoduleConfig = contents.find(content =>
/url.*insteadOf.*org-123456@github.com:/ content.includes('url.https://github.com/.insteadOf')
)
expect(submoduleConfig).toContain(
'url.https://github.com/.insteadOf git@github.com:'
)
expect(submoduleConfig).toContain(
'url.https://github.com/.insteadOf org-123456@github.com:'
) )
} }
) )
@ -656,11 +659,8 @@ describe('git-auth-helper tests', () => {
// Assert // Assert
// Should configure sshCommand (1 call) // Should configure sshCommand (1 call)
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(2) expect(mockSubmoduleForeach).toHaveBeenCalledTimes(1)
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(/core\.sshCommand/)
/unset-all.*insteadOf/
)
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/core\.sshCommand/)
} }
) )
@ -812,10 +812,11 @@ describe('git-auth-helper tests', () => {
}) })
const removeAuth_removesTokenFromSubmodules = const removeAuth_removesTokenFromSubmodules =
'removeAuth removes token from submodules' 'post removeAuth removes token and rewrites from submodules'
it(removeAuth_removesTokenFromSubmodules, async () => { it(removeAuth_removesTokenFromSubmodules, async () => {
// Arrange // Arrange
await setup(removeAuth_removesTokenFromSubmodules) await setup(removeAuth_removesTokenFromSubmodules)
settings.sshKey = ''
// Create fake submodule config paths // Create fake submodule config paths
const submodule1Dir = path.join(workspace, '.git', 'modules', 'submodule-1') const submodule1Dir = path.join(workspace, '.git', 'modules', 'submodule-1')
@ -844,14 +845,19 @@ describe('git-auth-helper tests', () => {
let credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter( let credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
f => f.startsWith('git-credentials-') && f.endsWith('.config') f => f.startsWith('git-credentials-') && f.endsWith('.config')
) )
expect(credentialsFiles.length).toBe(1) expect(credentialsFiles.length).toBe(2)
const credentialsFilePath = path.join(runnerTemp, credentialsFiles[0]) const submodule1GitDir = submodule1Dir.replace(/\\/g, '/')
const [credentialsFilePath] = await git.tryGetConfigValues(
`includeIf.gitdir:${submodule1GitDir}.path`,
false,
submodule1ConfigPath
)
expect(credentialsFilePath).toBeTruthy()
// Verify submodule 1 config has includeIf entries // Verify submodule 1 config has includeIf entries
let submodule1Content = ( let submodule1Content = (
await fs.promises.readFile(submodule1ConfigPath) await fs.promises.readFile(submodule1ConfigPath)
).toString() ).toString()
const submodule1GitDir = submodule1Dir.replace(/\\/g, '/')
expect( expect(
submodule1Content.indexOf(`includeIf.gitdir:${submodule1GitDir}.path`) submodule1Content.indexOf(`includeIf.gitdir:${submodule1GitDir}.path`)
).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
@ -883,12 +889,16 @@ describe('git-auth-helper tests', () => {
submodule2Content.indexOf(containerCredentialsPath) submodule2Content.indexOf(containerCredentialsPath)
).toBeGreaterThanOrEqual(0) ).toBeGreaterThanOrEqual(0)
// Act - ensure mock persists for removeAuth // Restore saved state in a fresh helper, as the post action does.
mockGetSubmoduleConfigPaths.mockResolvedValue([ const savedPaths = jest.mocked(stateHelper.setCredentialsConfigPaths).mock
submodule1ConfigPath, .calls[1][0]
submodule2ConfigPath expect(savedPaths.map(file => path.basename(file)).sort()).toEqual(
]) credentialsFiles.sort()
await authHelper.removeAuth() )
stateHelper.CredentialsConfigPaths.push(
...JSON.parse(JSON.stringify(savedPaths))
)
await gitAuthHelper.createAuthHelper(git).removeAuth()
// Assert submodule 1 includeIf entries removed // Assert submodule 1 includeIf entries removed
submodule1Content = ( submodule1Content = (
@ -923,6 +933,42 @@ describe('git-auth-helper tests', () => {
} }
}) })
it.each(['copy', 'rewrite'])(
'post removeAuth cleans up saved paths after submodule %s failure',
async stage => {
await setup(`post cleanup after ${stage} failure`)
settings.sshKey = ''
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const [mainPath] = jest.mocked(stateHelper.setCredentialsConfigPaths).mock
.calls[0][0]
const error = new Error('Submodule setup failed')
if (stage === 'copy') {
jest.spyOn(fs.promises, 'copyFile').mockRejectedValueOnce(error)
} else {
jest.mocked(git.config).mockRejectedValueOnce(error)
}
await expect(authHelper.configureSubmoduleAuth()).rejects.toThrow(error)
const savedPaths = jest.mocked(stateHelper.setCredentialsConfigPaths).mock
.calls[1][0]
expect(savedPaths).toHaveLength(2)
expect(savedPaths[0]).toBe(mainPath)
const existingPaths = stage === 'copy' ? [mainPath] : savedPaths
expect((await fs.promises.readdir(runnerTemp)).sort()).toEqual(
existingPaths.map(file => path.basename(file)).sort()
)
// No includes are discoverable; cleanup must use only the saved state.
await fs.promises.writeFile(localGitConfigPath, '')
stateHelper.CredentialsConfigPaths.push(
...JSON.parse(JSON.stringify(savedPaths))
)
await gitAuthHelper.createAuthHelper(git).removeAuth()
expect(await fs.promises.readdir(runnerTemp)).toEqual([])
}
)
const removeGlobalConfig_removesOverride = const removeGlobalConfig_removesOverride =
'removeGlobalConfig removes override' 'removeGlobalConfig removes override'
it(removeGlobalConfig_removesOverride, async () => { it(removeGlobalConfig_removesOverride, async () => {

71
dist/index.js vendored
View File

@ -34926,6 +34926,13 @@ const SshKeyPath = getState('sshKeyPath');
* The SSH known hosts path for the POST action. The value is empty during the MAIN action. * The SSH known hosts path for the POST action. The value is empty during the MAIN action.
*/ */
const SshKnownHostsPath = getState('sshKnownHostsPath'); const SshKnownHostsPath = getState('sshKnownHostsPath');
/**
* Credentials config files owned by this action, including incomplete setup.
*/
const CredentialsConfigPaths = JSON.parse(getState('credentialsConfigPaths') || '[]');
function setCredentialsConfigPaths(credentialsConfigPaths) {
saveState('credentialsConfigPaths', credentialsConfigPaths);
}
/** /**
* Save the repository path so the POST action can retrieve the value. * Save the repository path so the POST action can retrieve the value.
*/ */
@ -35051,6 +35058,7 @@ class GitAuthHelper {
sshKnownHostsPath = ''; sshKnownHostsPath = '';
temporaryHomePath = ''; temporaryHomePath = '';
credentialsConfigPath = ''; // Path to separate credentials config file in RUNNER_TEMP credentialsConfigPath = ''; // Path to separate credentials config file in RUNNER_TEMP
submoduleCredentialsConfigPath = '';
constructor(gitCommandManager, gitSourceSettings) { constructor(gitCommandManager, gitSourceSettings) {
this.git = gitCommandManager; this.git = gitCommandManager;
this.settings = gitSourceSettings || {}; this.settings = gitSourceSettings || {};
@ -35135,11 +35143,15 @@ class GitAuthHelper {
} }
} }
async configureSubmoduleAuth() { async configureSubmoduleAuth() {
// Remove possible previous HTTPS instead of SSH
await this.removeSubmoduleGitConfig(this.insteadOfKey);
if (this.settings.persistCredentials) { if (this.settings.persistCredentials) {
// Get the credentials config file path in RUNNER_TEMP const credentialsConfigPath = this.getCredentialsConfigPath(!this.settings.sshKey);
const credentialsConfigPath = this.getCredentialsConfigPath(); if (!this.settings.sshKey) {
await external_fs_namespaceObject.promises.copyFile(this.getCredentialsConfigPath(), credentialsConfigPath);
// Keep rewrites with their credentials, without changing user config or main-repository URLs.
for (const insteadOfValue of this.insteadOfValues) {
await this.git.config(this.insteadOfKey, insteadOfValue, false, true, credentialsConfigPath);
}
}
// Container credentials config path // Container credentials config path
const containerCredentialsPath = external_path_namespaceObject.posix.join('/github/runner_temp', external_path_namespaceObject.basename(credentialsConfigPath)); const containerCredentialsPath = external_path_namespaceObject.posix.join('/github/runner_temp', external_path_namespaceObject.basename(credentialsConfigPath));
// Get submodule config file paths. // Get submodule config file paths.
@ -35151,9 +35163,7 @@ class GitAuthHelper {
let submoduleGitDir = external_path_namespaceObject.dirname(configPath); // The config file is at .git/modules/submodule-name/config let submoduleGitDir = external_path_namespaceObject.dirname(configPath); // The config file is at .git/modules/submodule-name/config
submoduleGitDir = submoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows submoduleGitDir = submoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
// Configure host includeIf // Configure host includeIf
await this.git.config(`includeIf.gitdir:${submoduleGitDir}.path`, credentialsConfigPath, false, // globalConfig? await this.configureSubmoduleIncludeIf(`includeIf.gitdir:${submoduleGitDir}.path`, credentialsConfigPath, configPath);
false, // add?
configPath);
// Container submodule git directory // Container submodule git directory
const githubWorkspace = process.env['GITHUB_WORKSPACE']; const githubWorkspace = process.env['GITHUB_WORKSPACE'];
external_assert_.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined'); external_assert_.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined');
@ -35161,20 +35171,12 @@ class GitAuthHelper {
relativeSubmoduleGitDir = relativeSubmoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows relativeSubmoduleGitDir = relativeSubmoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
const containerSubmoduleGitDir = external_path_namespaceObject.posix.join('/github/workspace', relativeSubmoduleGitDir); const containerSubmoduleGitDir = external_path_namespaceObject.posix.join('/github/workspace', relativeSubmoduleGitDir);
// Configure container includeIf // Configure container includeIf
await this.git.config(`includeIf.gitdir:${containerSubmoduleGitDir}.path`, containerCredentialsPath, false, // globalConfig? await this.configureSubmoduleIncludeIf(`includeIf.gitdir:${containerSubmoduleGitDir}.path`, containerCredentialsPath, configPath);
false, // add?
configPath);
} }
if (this.settings.sshKey) { if (this.settings.sshKey) {
// Configure core.sshCommand // Configure core.sshCommand
await this.git.submoduleForeach(`git config --local '${SSH_COMMAND_KEY}' '${this.sshCommand}'`, this.settings.nestedSubmodules); await this.git.submoduleForeach(`git config --local '${SSH_COMMAND_KEY}' '${this.sshCommand}'`, this.settings.nestedSubmodules);
} }
else {
// Configure HTTPS instead of SSH
for (const insteadOfValue of this.insteadOfValues) {
await this.git.submoduleForeach(`git config --local --add '${this.insteadOfKey}' '${insteadOfValue}'`, this.settings.nestedSubmodules);
}
}
} }
} }
async removeAuth() { async removeAuth() {
@ -35188,6 +35190,12 @@ class GitAuthHelper {
await rmRF(this.temporaryHomePath); await rmRF(this.temporaryHomePath);
} }
} }
async configureSubmoduleIncludeIf(key, value, configPath) {
const values = await this.git.tryGetConfigValues(key, false, configPath);
if (!values.includes(value)) {
await this.git.config(key, value, false, true, configPath);
}
}
/** /**
* Configures SSH authentication by writing the SSH key and known hosts, * Configures SSH authentication by writing the SSH key and known hosts,
* and setting up the GIT_SSH_COMMAND environment variable. * and setting up the GIT_SSH_COMMAND environment variable.
@ -35305,20 +35313,31 @@ class GitAuthHelper {
} }
} }
/** /**
* Gets or creates the path to the credentials config file in RUNNER_TEMP. * Gets or creates the path to a credentials config file in RUNNER_TEMP.
* @returns The absolute path to the credentials config file * @returns The absolute path to the credentials config file
*/ */
getCredentialsConfigPath() { getCredentialsConfigPath(forSubmodules = false) {
if (this.credentialsConfigPath) { const existingPath = forSubmodules
return this.credentialsConfigPath; ? this.submoduleCredentialsConfigPath
: this.credentialsConfigPath;
if (existingPath) {
return existingPath;
} }
const runnerTemp = process.env['RUNNER_TEMP'] || ''; const runnerTemp = process.env['RUNNER_TEMP'] || '';
external_assert_.ok(runnerTemp, 'RUNNER_TEMP is not defined'); external_assert_.ok(runnerTemp, 'RUNNER_TEMP is not defined');
// Create a unique filename for this checkout instance // Create a unique filename for this checkout instance
const configFileName = `git-credentials-${(0,external_crypto_namespaceObject.randomUUID)()}.config`; const configFileName = `git-credentials-${(0,external_crypto_namespaceObject.randomUUID)()}.config`;
this.credentialsConfigPath = external_path_namespaceObject.join(runnerTemp, configFileName); const configPath = external_path_namespaceObject.join(runnerTemp, configFileName);
core_debug(`Credentials config path: ${this.credentialsConfigPath}`); if (forSubmodules) {
return this.credentialsConfigPath; this.submoduleCredentialsConfigPath = configPath;
}
else {
this.credentialsConfigPath = configPath;
}
// Save before writing so post cleanup also handles incomplete setup or removed submodules.
setCredentialsConfigPaths([this.credentialsConfigPath, this.submoduleCredentialsConfigPath].filter(Boolean));
core_debug(`Credentials config path: ${configPath}`);
return configPath;
} }
/** /**
* Removes SSH authentication configuration by cleaning up SSH keys, * Removes SSH authentication configuration by cleaning up SSH keys,
@ -35364,7 +35383,11 @@ class GitAuthHelper {
await this.removeGitConfig(this.tokenConfigKey); await this.removeGitConfig(this.tokenConfigKey);
await this.removeSubmoduleGitConfig(this.tokenConfigKey); await this.removeSubmoduleGitConfig(this.tokenConfigKey);
// Collect credentials config paths that need to be removed // Collect credentials config paths that need to be removed
const credentialsPaths = new Set(); const credentialsPaths = new Set([
this.credentialsConfigPath,
this.submoduleCredentialsConfigPath,
...CredentialsConfigPaths
].filter(Boolean));
// Remove includeIf entries that point to git-credentials-*.config files // Remove includeIf entries that point to git-credentials-*.config files
info('Removing includeIf entries pointing to credentials config files'); info('Removing includeIf entries pointing to credentials config files');
const mainCredentialsPaths = await this.removeIncludeIfCredentials(); const mainCredentialsPaths = await this.removeIncludeIfCredentials();

View File

@ -44,6 +44,7 @@ class GitAuthHelper {
private sshKnownHostsPath = '' private sshKnownHostsPath = ''
private temporaryHomePath = '' private temporaryHomePath = ''
private credentialsConfigPath = '' // Path to separate credentials config file in RUNNER_TEMP private credentialsConfigPath = '' // Path to separate credentials config file in RUNNER_TEMP
private submoduleCredentialsConfigPath = ''
constructor( constructor(
gitCommandManager: IGitCommandManager, gitCommandManager: IGitCommandManager,
@ -155,12 +156,27 @@ class GitAuthHelper {
} }
async configureSubmoduleAuth(): Promise<void> { async configureSubmoduleAuth(): Promise<void> {
// Remove possible previous HTTPS instead of SSH
await this.removeSubmoduleGitConfig(this.insteadOfKey)
if (this.settings.persistCredentials) { if (this.settings.persistCredentials) {
// Get the credentials config file path in RUNNER_TEMP const credentialsConfigPath = this.getCredentialsConfigPath(
const credentialsConfigPath = this.getCredentialsConfigPath() !this.settings.sshKey
)
if (!this.settings.sshKey) {
await fs.promises.copyFile(
this.getCredentialsConfigPath(),
credentialsConfigPath
)
// Keep rewrites with their credentials, without changing user config or main-repository URLs.
for (const insteadOfValue of this.insteadOfValues) {
await this.git.config(
this.insteadOfKey,
insteadOfValue,
false,
true,
credentialsConfigPath
)
}
}
// Container credentials config path // Container credentials config path
const containerCredentialsPath = path.posix.join( const containerCredentialsPath = path.posix.join(
@ -181,11 +197,9 @@ class GitAuthHelper {
submoduleGitDir = submoduleGitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows submoduleGitDir = submoduleGitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows
// Configure host includeIf // Configure host includeIf
await this.git.config( await this.configureSubmoduleIncludeIf(
`includeIf.gitdir:${submoduleGitDir}.path`, `includeIf.gitdir:${submoduleGitDir}.path`,
credentialsConfigPath, credentialsConfigPath,
false, // globalConfig?
false, // add?
configPath configPath
) )
@ -203,11 +217,9 @@ class GitAuthHelper {
) )
// Configure container includeIf // Configure container includeIf
await this.git.config( await this.configureSubmoduleIncludeIf(
`includeIf.gitdir:${containerSubmoduleGitDir}.path`, `includeIf.gitdir:${containerSubmoduleGitDir}.path`,
containerCredentialsPath, containerCredentialsPath,
false, // globalConfig?
false, // add?
configPath configPath
) )
} }
@ -218,14 +230,6 @@ class GitAuthHelper {
`git config --local '${SSH_COMMAND_KEY}' '${this.sshCommand}'`, `git config --local '${SSH_COMMAND_KEY}' '${this.sshCommand}'`,
this.settings.nestedSubmodules this.settings.nestedSubmodules
) )
} else {
// Configure HTTPS instead of SSH
for (const insteadOfValue of this.insteadOfValues) {
await this.git.submoduleForeach(
`git config --local --add '${this.insteadOfKey}' '${insteadOfValue}'`,
this.settings.nestedSubmodules
)
}
} }
} }
} }
@ -243,6 +247,17 @@ class GitAuthHelper {
} }
} }
private async configureSubmoduleIncludeIf(
key: string,
value: string,
configPath: string
): Promise<void> {
const values = await this.git.tryGetConfigValues(key, false, configPath)
if (!values.includes(value)) {
await this.git.config(key, value, false, true, configPath)
}
}
/** /**
* Configures SSH authentication by writing the SSH key and known hosts, * Configures SSH authentication by writing the SSH key and known hosts,
* and setting up the GIT_SSH_COMMAND environment variable. * and setting up the GIT_SSH_COMMAND environment variable.
@ -410,12 +425,15 @@ class GitAuthHelper {
} }
/** /**
* Gets or creates the path to the credentials config file in RUNNER_TEMP. * Gets or creates the path to a credentials config file in RUNNER_TEMP.
* @returns The absolute path to the credentials config file * @returns The absolute path to the credentials config file
*/ */
private getCredentialsConfigPath(): string { private getCredentialsConfigPath(forSubmodules = false): string {
if (this.credentialsConfigPath) { const existingPath = forSubmodules
return this.credentialsConfigPath ? this.submoduleCredentialsConfigPath
: this.credentialsConfigPath
if (existingPath) {
return existingPath
} }
const runnerTemp = process.env['RUNNER_TEMP'] || '' const runnerTemp = process.env['RUNNER_TEMP'] || ''
@ -423,10 +441,21 @@ class GitAuthHelper {
// Create a unique filename for this checkout instance // Create a unique filename for this checkout instance
const configFileName = `git-credentials-${randomUUID()}.config` const configFileName = `git-credentials-${randomUUID()}.config`
this.credentialsConfigPath = path.join(runnerTemp, configFileName) const configPath = path.join(runnerTemp, configFileName)
if (forSubmodules) {
this.submoduleCredentialsConfigPath = configPath
} else {
this.credentialsConfigPath = configPath
}
// Save before writing so post cleanup also handles incomplete setup or removed submodules.
stateHelper.setCredentialsConfigPaths(
[this.credentialsConfigPath, this.submoduleCredentialsConfigPath].filter(
Boolean
)
)
core.debug(`Credentials config path: ${this.credentialsConfigPath}`) core.debug(`Credentials config path: ${configPath}`)
return this.credentialsConfigPath return configPath
} }
/** /**
@ -476,7 +505,13 @@ class GitAuthHelper {
await this.removeSubmoduleGitConfig(this.tokenConfigKey) await this.removeSubmoduleGitConfig(this.tokenConfigKey)
// Collect credentials config paths that need to be removed // Collect credentials config paths that need to be removed
const credentialsPaths = new Set<string>() const credentialsPaths = new Set(
[
this.credentialsConfigPath,
this.submoduleCredentialsConfigPath,
...stateHelper.CredentialsConfigPaths
].filter(Boolean)
)
// Remove includeIf entries that point to git-credentials-*.config files // Remove includeIf entries that point to git-credentials-*.config files
core.info('Removing includeIf entries pointing to credentials config files') core.info('Removing includeIf entries pointing to credentials config files')

View File

@ -25,6 +25,17 @@ export const SshKeyPath = core.getState('sshKeyPath')
*/ */
export const SshKnownHostsPath = core.getState('sshKnownHostsPath') export const SshKnownHostsPath = core.getState('sshKnownHostsPath')
/**
* Credentials config files owned by this action, including incomplete setup.
*/
export const CredentialsConfigPaths: string[] = JSON.parse(
core.getState('credentialsConfigPaths') || '[]'
)
export function setCredentialsConfigPaths(credentialsConfigPaths: string[]) {
core.saveState('credentialsConfigPaths', credentialsConfigPaths)
}
/** /**
* Save the repository path so the POST action can retrieve the value. * Save the repository path so the POST action can retrieve the value.
*/ */