Skip to content

Commit 93cb6ef

Browse files
authored
Cleanup actions/checkout@v6 auth style (#2301)
1 parent 08c6903 commit 93cb6ef

File tree

5 files changed

+657
-2
lines changed

5 files changed

+657
-2
lines changed

__test__/git-auth-helper.test.ts

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,283 @@ describe('git-auth-helper tests', () => {
675675
expect(gitConfigContent.indexOf('http.')).toBeLessThan(0)
676676
})
677677

678+
const removeAuth_removesV6StyleCredentials =
679+
'removeAuth removes v6 style credentials'
680+
it(removeAuth_removesV6StyleCredentials, async () => {
681+
// Arrange
682+
await setup(removeAuth_removesV6StyleCredentials)
683+
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
684+
await authHelper.configureAuth()
685+
686+
// Manually create v6-style credentials that would be left by v6
687+
const credentialsFileName =
688+
'git-credentials-12345678-1234-1234-1234-123456789abc.config'
689+
const credentialsFilePath = path.join(runnerTemp, credentialsFileName)
690+
const basicCredential = Buffer.from(
691+
`x-access-token:${settings.authToken}`,
692+
'utf8'
693+
).toString('base64')
694+
const credentialsContent = `[http "https://github.com/"]\n\textraheader = AUTHORIZATION: basic ${basicCredential}\n`
695+
await fs.promises.writeFile(credentialsFilePath, credentialsContent)
696+
697+
// Add includeIf entries to local git config (simulating v6 configuration)
698+
const hostGitDir = path.join(workspace, '.git').replace(/\\/g, '/')
699+
await fs.promises.appendFile(
700+
localGitConfigPath,
701+
`[includeIf "gitdir:${hostGitDir}/"]\n\tpath = ${credentialsFilePath}\n`
702+
)
703+
await fs.promises.appendFile(
704+
localGitConfigPath,
705+
`[includeIf "gitdir:/github/workspace/.git/"]\n\tpath = /github/runner_temp/${credentialsFileName}\n`
706+
)
707+
708+
// Verify v6 style config exists
709+
let gitConfigContent = (
710+
await fs.promises.readFile(localGitConfigPath)
711+
).toString()
712+
expect(gitConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(0)
713+
expect(
714+
gitConfigContent.indexOf(credentialsFilePath)
715+
).toBeGreaterThanOrEqual(0)
716+
await fs.promises.stat(credentialsFilePath) // Verify file exists
717+
718+
// Mock the git methods to handle v6 cleanup
719+
const mockTryGetConfigKeys = git.tryGetConfigKeys as jest.Mock<any, any>
720+
mockTryGetConfigKeys.mockResolvedValue([
721+
`includeIf.gitdir:${hostGitDir}/.path`,
722+
'includeIf.gitdir:/github/workspace/.git/.path'
723+
])
724+
725+
const mockTryGetConfigValues = git.tryGetConfigValues as jest.Mock<any, any>
726+
mockTryGetConfigValues.mockImplementation(async (key: string) => {
727+
if (key === `includeIf.gitdir:${hostGitDir}/.path`) {
728+
return [credentialsFilePath]
729+
}
730+
if (key === 'includeIf.gitdir:/github/workspace/.git/.path') {
731+
return [`/github/runner_temp/${credentialsFileName}`]
732+
}
733+
return []
734+
})
735+
736+
const mockTryConfigUnsetValue = git.tryConfigUnsetValue as jest.Mock<
737+
any,
738+
any
739+
>
740+
mockTryConfigUnsetValue.mockImplementation(
741+
async (
742+
key: string,
743+
value: string,
744+
globalConfig?: boolean,
745+
configPath?: string
746+
) => {
747+
const targetPath = configPath || localGitConfigPath
748+
let content = await fs.promises.readFile(targetPath, 'utf8')
749+
// Remove the includeIf section
750+
const lines = content
751+
.split('\n')
752+
.filter(line => !line.includes('includeIf') && !line.includes(value))
753+
await fs.promises.writeFile(targetPath, lines.join('\n'))
754+
return true
755+
}
756+
)
757+
758+
// Act
759+
await authHelper.removeAuth()
760+
761+
// Assert includeIf entries removed from local git config
762+
gitConfigContent = (
763+
await fs.promises.readFile(localGitConfigPath)
764+
).toString()
765+
expect(gitConfigContent.indexOf('includeIf')).toBeLessThan(0)
766+
expect(gitConfigContent.indexOf(credentialsFilePath)).toBeLessThan(0)
767+
768+
// Assert credentials config file deleted
769+
try {
770+
await fs.promises.stat(credentialsFilePath)
771+
throw new Error('Credentials file should have been deleted')
772+
} catch (err) {
773+
if ((err as any)?.code !== 'ENOENT') {
774+
throw err
775+
}
776+
}
777+
})
778+
779+
const removeAuth_removesV6StyleCredentialsFromSubmodules =
780+
'removeAuth removes v6 style credentials from submodules'
781+
it(removeAuth_removesV6StyleCredentialsFromSubmodules, async () => {
782+
// Arrange
783+
await setup(removeAuth_removesV6StyleCredentialsFromSubmodules)
784+
785+
// Create fake submodule config paths
786+
const submodule1Dir = path.join(workspace, '.git', 'modules', 'submodule-1')
787+
const submodule1ConfigPath = path.join(submodule1Dir, 'config')
788+
await fs.promises.mkdir(submodule1Dir, {recursive: true})
789+
await fs.promises.writeFile(submodule1ConfigPath, '')
790+
791+
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
792+
await authHelper.configureAuth()
793+
794+
// Create v6-style credentials file
795+
const credentialsFileName =
796+
'git-credentials-abcdef12-3456-7890-abcd-ef1234567890.config'
797+
const credentialsFilePath = path.join(runnerTemp, credentialsFileName)
798+
const basicCredential = Buffer.from(
799+
`x-access-token:${settings.authToken}`,
800+
'utf8'
801+
).toString('base64')
802+
const credentialsContent = `[http "https://github.com/"]\n\textraheader = AUTHORIZATION: basic ${basicCredential}\n`
803+
await fs.promises.writeFile(credentialsFilePath, credentialsContent)
804+
805+
// Add includeIf entries to submodule config
806+
const submodule1GitDir = submodule1Dir.replace(/\\/g, '/')
807+
await fs.promises.appendFile(
808+
submodule1ConfigPath,
809+
`[includeIf "gitdir:${submodule1GitDir}/"]\n\tpath = ${credentialsFilePath}\n`
810+
)
811+
812+
// Verify submodule config has includeIf entry
813+
let submoduleConfigContent = (
814+
await fs.promises.readFile(submodule1ConfigPath)
815+
).toString()
816+
expect(submoduleConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(
817+
0
818+
)
819+
expect(
820+
submoduleConfigContent.indexOf(credentialsFilePath)
821+
).toBeGreaterThanOrEqual(0)
822+
823+
// Mock getSubmoduleConfigPaths
824+
const mockGetSubmoduleConfigPaths =
825+
git.getSubmoduleConfigPaths as jest.Mock<any, any>
826+
mockGetSubmoduleConfigPaths.mockResolvedValue([submodule1ConfigPath])
827+
828+
// Mock tryGetConfigKeys for submodule
829+
const mockTryGetConfigKeys = git.tryGetConfigKeys as jest.Mock<any, any>
830+
mockTryGetConfigKeys.mockImplementation(
831+
async (pattern: string, globalConfig?: boolean, configPath?: string) => {
832+
if (configPath === submodule1ConfigPath) {
833+
return [`includeIf.gitdir:${submodule1GitDir}/.path`]
834+
}
835+
return []
836+
}
837+
)
838+
839+
// Mock tryGetConfigValues for submodule
840+
const mockTryGetConfigValues = git.tryGetConfigValues as jest.Mock<any, any>
841+
mockTryGetConfigValues.mockImplementation(
842+
async (key: string, globalConfig?: boolean, configPath?: string) => {
843+
if (
844+
configPath === submodule1ConfigPath &&
845+
key === `includeIf.gitdir:${submodule1GitDir}/.path`
846+
) {
847+
return [credentialsFilePath]
848+
}
849+
return []
850+
}
851+
)
852+
853+
// Mock tryConfigUnsetValue for submodule
854+
const mockTryConfigUnsetValue = git.tryConfigUnsetValue as jest.Mock<
855+
any,
856+
any
857+
>
858+
mockTryConfigUnsetValue.mockImplementation(
859+
async (
860+
key: string,
861+
value: string,
862+
globalConfig?: boolean,
863+
configPath?: string
864+
) => {
865+
const targetPath = configPath || localGitConfigPath
866+
let content = await fs.promises.readFile(targetPath, 'utf8')
867+
const lines = content
868+
.split('\n')
869+
.filter(line => !line.includes('includeIf') && !line.includes(value))
870+
await fs.promises.writeFile(targetPath, lines.join('\n'))
871+
return true
872+
}
873+
)
874+
875+
// Act
876+
await authHelper.removeAuth()
877+
878+
// Assert submodule includeIf entries removed
879+
submoduleConfigContent = (
880+
await fs.promises.readFile(submodule1ConfigPath)
881+
).toString()
882+
expect(submoduleConfigContent.indexOf('includeIf')).toBeLessThan(0)
883+
expect(submoduleConfigContent.indexOf(credentialsFilePath)).toBeLessThan(0)
884+
885+
// Assert credentials file deleted
886+
try {
887+
await fs.promises.stat(credentialsFilePath)
888+
throw new Error('Credentials file should have been deleted')
889+
} catch (err) {
890+
if ((err as any)?.code !== 'ENOENT') {
891+
throw err
892+
}
893+
}
894+
})
895+
896+
const removeAuth_skipsV6CleanupWhenEnvVarSet =
897+
'removeAuth skips v6 cleanup when ACTIONS_CHECKOUT_SKIP_V6_CLEANUP is set'
898+
it(removeAuth_skipsV6CleanupWhenEnvVarSet, async () => {
899+
// Arrange
900+
await setup(removeAuth_skipsV6CleanupWhenEnvVarSet)
901+
902+
// Set the skip environment variable
903+
process.env['ACTIONS_CHECKOUT_SKIP_V6_CLEANUP'] = '1'
904+
905+
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
906+
await authHelper.configureAuth()
907+
908+
// Create v6-style credentials file in RUNNER_TEMP
909+
const credentialsFileName = 'git-credentials-test-uuid-1234-5678.config'
910+
const credentialsFilePath = path.join(runnerTemp, credentialsFileName)
911+
const credentialsContent =
912+
'[http "https://github.com/"]\n\textraheader = AUTHORIZATION: basic token\n'
913+
await fs.promises.writeFile(credentialsFilePath, credentialsContent)
914+
915+
// Add includeIf section to local git config (separate from http.* config)
916+
const includeIfSection = `\n[includeIf "gitdir:/some/path/.git/"]\n\tpath = ${credentialsFilePath}\n`
917+
await fs.promises.appendFile(localGitConfigPath, includeIfSection)
918+
919+
// Verify v6 style config exists
920+
let gitConfigContent = (
921+
await fs.promises.readFile(localGitConfigPath)
922+
).toString()
923+
expect(gitConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(0)
924+
await fs.promises.stat(credentialsFilePath) // Verify file exists
925+
926+
// Act
927+
await authHelper.removeAuth()
928+
929+
// Assert v5 cleanup still happened (http.* removed)
930+
gitConfigContent = (
931+
await fs.promises.readFile(localGitConfigPath)
932+
).toString()
933+
expect(
934+
gitConfigContent.indexOf('http.https://github.com/.extraheader')
935+
).toBeLessThan(0)
936+
937+
// Assert v6 cleanup was skipped - includeIf should still be present
938+
expect(gitConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(0)
939+
expect(
940+
gitConfigContent.indexOf(credentialsFilePath)
941+
).toBeGreaterThanOrEqual(0)
942+
943+
// Assert credentials file still exists (wasn't deleted)
944+
await fs.promises.stat(credentialsFilePath) // File should still exist
945+
946+
// Assert debug message was logged
947+
expect(core.debug).toHaveBeenCalledWith(
948+
'Skipping v6 style cleanup due to ACTIONS_CHECKOUT_SKIP_V6_CLEANUP'
949+
)
950+
951+
// Cleanup
952+
delete process.env['ACTIONS_CHECKOUT_SKIP_V6_CLEANUP']
953+
})
954+
678955
const removeGlobalConfig_removesOverride =
679956
'removeGlobalConfig removes override'
680957
it(removeGlobalConfig_removesOverride, async () => {
@@ -796,6 +1073,18 @@ async function setup(testName: string): Promise<void> {
7961073
),
7971074
tryDisableAutomaticGarbageCollection: jest.fn(),
7981075
tryGetFetchUrl: jest.fn(),
1076+
getSubmoduleConfigPaths: jest.fn(async () => {
1077+
return []
1078+
}),
1079+
tryConfigUnsetValue: jest.fn(async () => {
1080+
return true
1081+
}),
1082+
tryGetConfigValues: jest.fn(async () => {
1083+
return []
1084+
}),
1085+
tryGetConfigKeys: jest.fn(async () => {
1086+
return []
1087+
}),
7991088
tryReset: jest.fn(),
8001089
version: jest.fn()
8011090
}

__test__/git-directory-helper.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,18 @@ async function setup(testName: string): Promise<void> {
499499
await fs.promises.stat(path.join(repositoryPath, '.git'))
500500
return repositoryUrl
501501
}),
502+
getSubmoduleConfigPaths: jest.fn(async () => {
503+
return []
504+
}),
505+
tryConfigUnsetValue: jest.fn(async () => {
506+
return true
507+
}),
508+
tryGetConfigValues: jest.fn(async () => {
509+
return []
510+
}),
511+
tryGetConfigKeys: jest.fn(async () => {
512+
return []
513+
}),
502514
tryReset: jest.fn(async () => {
503515
return true
504516
}),

0 commit comments

Comments
 (0)