Skip to content

Commit 89950f9

Browse files
committed
Cleanup actions/checkout@v6 auth style
1 parent 08c6903 commit 89950f9

5 files changed

Lines changed: 380 additions & 2 deletions

File tree

__test__/git-auth-helper.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,18 @@ async function setup(testName: string): Promise<void> {
796796
),
797797
tryDisableAutomaticGarbageCollection: jest.fn(),
798798
tryGetFetchUrl: jest.fn(),
799+
getSubmoduleConfigPaths: jest.fn(async () => {
800+
return []
801+
}),
802+
tryConfigUnsetValue: jest.fn(async () => {
803+
return true
804+
}),
805+
tryGetConfigValues: jest.fn(async () => {
806+
return []
807+
}),
808+
tryGetConfigKeys: jest.fn(async () => {
809+
return []
810+
}),
799811
tryReset: jest.fn(),
800812
version: jest.fn()
801813
}

__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
}),

dist/index.js

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,50 @@ class GitAuthHelper {
411411
}
412412
removeToken() {
413413
return __awaiter(this, void 0, void 0, function* () {
414-
// HTTP extra header
414+
// Remove HTTP extra header from local git config and submodule configs
415415
yield this.removeGitConfig(this.tokenConfigKey);
416+
//
417+
// Cleanup actions/checkout@v6 style credentials
418+
//
419+
const skipV6Cleanup = process.env['ACTIONS_CHECKOUT_SKIP_V6_CLEANUP'];
420+
if (skipV6Cleanup === '1' || (skipV6Cleanup === null || skipV6Cleanup === void 0 ? void 0 : skipV6Cleanup.toLowerCase()) === 'true') {
421+
core.debug('Skipping v6 style cleanup due to ACTIONS_CHECKOUT_SKIP_V6_CLEANUP');
422+
return;
423+
}
424+
try {
425+
// Collect credentials config paths that need to be removed
426+
const credentialsPaths = new Set();
427+
// Remove includeIf entries that point to git-credentials-*.config files
428+
const mainCredentialsPaths = yield this.removeIncludeIfCredentials();
429+
mainCredentialsPaths.forEach(path => credentialsPaths.add(path));
430+
// Remove submodule includeIf entries that point to git-credentials-*.config files
431+
try {
432+
const submoduleConfigPaths = yield this.git.getSubmoduleConfigPaths(true);
433+
for (const configPath of submoduleConfigPaths) {
434+
const submoduleCredentialsPaths = yield this.removeIncludeIfCredentials(configPath);
435+
submoduleCredentialsPaths.forEach(path => credentialsPaths.add(path));
436+
}
437+
}
438+
catch (err) {
439+
core.debug(`Unable to get submodule config paths: ${err}`);
440+
}
441+
// Remove credentials config files
442+
for (const credentialsPath of credentialsPaths) {
443+
// Only remove credentials config files if they are under RUNNER_TEMP
444+
const runnerTemp = process.env['RUNNER_TEMP'];
445+
if (runnerTemp && credentialsPath.startsWith(runnerTemp)) {
446+
try {
447+
yield io.rmRF(credentialsPath);
448+
}
449+
catch (err) {
450+
core.debug(`Failed to remove credentials config '${credentialsPath}': ${err}`);
451+
}
452+
}
453+
}
454+
}
455+
catch (err) {
456+
core.debug(`Failed to cleanup v6 style credentials: ${err}`);
457+
}
416458
});
417459
}
418460
removeGitConfig(configKey_1) {
@@ -430,6 +472,49 @@ class GitAuthHelper {
430472
`sh -c "git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :"`, true);
431473
});
432474
}
475+
/**
476+
* Removes includeIf entries that point to git-credentials-*.config files.
477+
* This handles cleanup of credentials configured by newer versions of the action.
478+
* @param configPath Optional path to a specific git config file to operate on
479+
* @returns Array of unique credentials config file paths that were found and removed
480+
*/
481+
removeIncludeIfCredentials(configPath) {
482+
return __awaiter(this, void 0, void 0, function* () {
483+
const credentialsPaths = new Set();
484+
try {
485+
// Get all includeIf.gitdir keys
486+
const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir:', false, // globalConfig?
487+
configPath);
488+
for (const key of keys) {
489+
// Get all values for this key
490+
const values = yield this.git.tryGetConfigValues(key, false, // globalConfig?
491+
configPath);
492+
if (values.length > 0) {
493+
// Remove only values that match git-credentials-<uuid>.config pattern
494+
for (const value of values) {
495+
if (this.testCredentialsConfigPath(value)) {
496+
credentialsPaths.add(value);
497+
yield this.git.tryConfigUnsetValue(key, value, false, configPath);
498+
}
499+
}
500+
}
501+
}
502+
}
503+
catch (err) {
504+
// Ignore errors - this is cleanup code
505+
core.debug(`Error during includeIf cleanup${configPath ? ` for ${configPath}` : ''}: ${err}`);
506+
}
507+
return Array.from(credentialsPaths);
508+
});
509+
}
510+
/**
511+
* Tests if a path matches the git-credentials-*.config pattern used by newer versions.
512+
* @param path The path to test
513+
* @returns True if the path matches the credentials config pattern
514+
*/
515+
testCredentialsConfigPath(path) {
516+
return /git-credentials-[0-9a-f-]+\.config$/i.test(path);
517+
}
433518
}
434519

435520

@@ -706,6 +791,16 @@ class GitCommandManager {
706791
throw new Error('Unexpected output when retrieving default branch');
707792
});
708793
}
794+
getSubmoduleConfigPaths(recursive) {
795+
return __awaiter(this, void 0, void 0, function* () {
796+
// Get submodule config file paths.
797+
// Use `--show-origin` to get the config file path for each submodule.
798+
const output = yield this.submoduleForeach(`git config --local --show-origin --name-only --get-regexp remote.origin.url`, recursive);
799+
// Extract config file paths from the output (lines starting with "file:").
800+
const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || [];
801+
return configPaths;
802+
});
803+
}
709804
getWorkingDirectory() {
710805
return this.workingDirectory;
711806
}
@@ -836,6 +931,20 @@ class GitCommandManager {
836931
return output.exitCode === 0;
837932
});
838933
}
934+
tryConfigUnsetValue(configKey, configValue, globalConfig, configFile) {
935+
return __awaiter(this, void 0, void 0, function* () {
936+
const args = ['config'];
937+
if (configFile) {
938+
args.push('--file', configFile);
939+
}
940+
else {
941+
args.push(globalConfig ? '--global' : '--local');
942+
}
943+
args.push('--unset', configKey, configValue);
944+
const output = yield this.execGit(args, true);
945+
return output.exitCode === 0;
946+
});
947+
}
839948
tryDisableAutomaticGarbageCollection() {
840949
return __awaiter(this, void 0, void 0, function* () {
841950
const output = yield this.execGit(['config', '--local', 'gc.auto', '0'], true);
@@ -855,6 +964,46 @@ class GitCommandManager {
855964
return stdout;
856965
});
857966
}
967+
tryGetConfigValues(configKey, globalConfig, configFile) {
968+
return __awaiter(this, void 0, void 0, function* () {
969+
const args = ['config'];
970+
if (configFile) {
971+
args.push('--file', configFile);
972+
}
973+
else {
974+
args.push(globalConfig ? '--global' : '--local');
975+
}
976+
args.push('--get-all', configKey);
977+
const output = yield this.execGit(args, true);
978+
if (output.exitCode !== 0) {
979+
return [];
980+
}
981+
return output.stdout
982+
.trim()
983+
.split('\n')
984+
.filter(value => value.trim());
985+
});
986+
}
987+
tryGetConfigKeys(pattern, globalConfig, configFile) {
988+
return __awaiter(this, void 0, void 0, function* () {
989+
const args = ['config'];
990+
if (configFile) {
991+
args.push('--file', configFile);
992+
}
993+
else {
994+
args.push(globalConfig ? '--global' : '--local');
995+
}
996+
args.push('--name-only', '--get-regexp', pattern);
997+
const output = yield this.execGit(args, true);
998+
if (output.exitCode !== 0) {
999+
return [];
1000+
}
1001+
return output.stdout
1002+
.trim()
1003+
.split('\n')
1004+
.filter(key => key.trim());
1005+
});
1006+
}
8581007
tryReset() {
8591008
return __awaiter(this, void 0, void 0, function* () {
8601009
const output = yield this.execGit(['reset', '--hard', 'HEAD'], true);

src/git-auth-helper.ts

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,58 @@ class GitAuthHelper {
346346
}
347347

348348
private async removeToken(): Promise<void> {
349-
// HTTP extra header
349+
// Remove HTTP extra header from local git config and submodule configs
350350
await this.removeGitConfig(this.tokenConfigKey)
351+
352+
//
353+
// Cleanup actions/checkout@v6 style credentials
354+
//
355+
const skipV6Cleanup = process.env['ACTIONS_CHECKOUT_SKIP_V6_CLEANUP']
356+
if (skipV6Cleanup === '1' || skipV6Cleanup?.toLowerCase() === 'true') {
357+
core.debug(
358+
'Skipping v6 style cleanup due to ACTIONS_CHECKOUT_SKIP_V6_CLEANUP'
359+
)
360+
return
361+
}
362+
363+
try {
364+
// Collect credentials config paths that need to be removed
365+
const credentialsPaths = new Set<string>()
366+
367+
// Remove includeIf entries that point to git-credentials-*.config files
368+
const mainCredentialsPaths = await this.removeIncludeIfCredentials()
369+
mainCredentialsPaths.forEach(path => credentialsPaths.add(path))
370+
371+
// Remove submodule includeIf entries that point to git-credentials-*.config files
372+
try {
373+
const submoduleConfigPaths =
374+
await this.git.getSubmoduleConfigPaths(true)
375+
for (const configPath of submoduleConfigPaths) {
376+
const submoduleCredentialsPaths =
377+
await this.removeIncludeIfCredentials(configPath)
378+
submoduleCredentialsPaths.forEach(path => credentialsPaths.add(path))
379+
}
380+
} catch (err) {
381+
core.debug(`Unable to get submodule config paths: ${err}`)
382+
}
383+
384+
// Remove credentials config files
385+
for (const credentialsPath of credentialsPaths) {
386+
// Only remove credentials config files if they are under RUNNER_TEMP
387+
const runnerTemp = process.env['RUNNER_TEMP']
388+
if (runnerTemp && credentialsPath.startsWith(runnerTemp)) {
389+
try {
390+
await io.rmRF(credentialsPath)
391+
} catch (err) {
392+
core.debug(
393+
`Failed to remove credentials config '${credentialsPath}': ${err}`
394+
)
395+
}
396+
}
397+
}
398+
} catch (err) {
399+
core.debug(`Failed to cleanup v6 style credentials: ${err}`)
400+
}
351401
}
352402

353403
private async removeGitConfig(
@@ -371,4 +421,59 @@ class GitAuthHelper {
371421
true
372422
)
373423
}
424+
425+
/**
426+
* Removes includeIf entries that point to git-credentials-*.config files.
427+
* This handles cleanup of credentials configured by newer versions of the action.
428+
* @param configPath Optional path to a specific git config file to operate on
429+
* @returns Array of unique credentials config file paths that were found and removed
430+
*/
431+
private async removeIncludeIfCredentials(
432+
configPath?: string
433+
): Promise<string[]> {
434+
const credentialsPaths = new Set<string>()
435+
436+
try {
437+
// Get all includeIf.gitdir keys
438+
const keys = await this.git.tryGetConfigKeys(
439+
'^includeIf\\.gitdir:',
440+
false, // globalConfig?
441+
configPath
442+
)
443+
444+
for (const key of keys) {
445+
// Get all values for this key
446+
const values = await this.git.tryGetConfigValues(
447+
key,
448+
false, // globalConfig?
449+
configPath
450+
)
451+
if (values.length > 0) {
452+
// Remove only values that match git-credentials-<uuid>.config pattern
453+
for (const value of values) {
454+
if (this.testCredentialsConfigPath(value)) {
455+
credentialsPaths.add(value)
456+
await this.git.tryConfigUnsetValue(key, value, false, configPath)
457+
}
458+
}
459+
}
460+
}
461+
} catch (err) {
462+
// Ignore errors - this is cleanup code
463+
core.debug(
464+
`Error during includeIf cleanup${configPath ? ` for ${configPath}` : ''}: ${err}`
465+
)
466+
}
467+
468+
return Array.from(credentialsPaths)
469+
}
470+
471+
/**
472+
* Tests if a path matches the git-credentials-*.config pattern used by newer versions.
473+
* @param path The path to test
474+
* @returns True if the path matches the credentials config pattern
475+
*/
476+
private testCredentialsConfigPath(path: string): boolean {
477+
return /git-credentials-[0-9a-f-]+\.config$/i.test(path)
478+
}
374479
}

0 commit comments

Comments
 (0)