Skip to content

Commit fdb36f5

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

File tree

5 files changed

+358
-2
lines changed

5 files changed

+358
-2
lines changed

__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: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,40 @@ 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+
// Collect credentials config paths that need to be removed
420+
const credentialsPaths = new Set();
421+
// Remove includeIf entries that point to git-credentials-*.config files
422+
const mainCredentialsPaths = yield this.removeIncludeIfCredentials();
423+
mainCredentialsPaths.forEach(path => credentialsPaths.add(path));
424+
// Remove submodule includeIf entries that point to git-credentials-*.config files
425+
try {
426+
const submoduleConfigPaths = yield this.git.getSubmoduleConfigPaths(true);
427+
for (const configPath of submoduleConfigPaths) {
428+
const submoduleCredentialsPaths = yield this.removeIncludeIfCredentials(configPath);
429+
submoduleCredentialsPaths.forEach(path => credentialsPaths.add(path));
430+
}
431+
}
432+
catch (err) {
433+
core.debug(`Unable to get submodule config paths: ${err}`);
434+
}
435+
// Remove credentials config files
436+
for (const credentialsPath of credentialsPaths) {
437+
// Only remove credentials config files if they are under RUNNER_TEMP
438+
const runnerTemp = process.env['RUNNER_TEMP'];
439+
if (runnerTemp && credentialsPath.startsWith(runnerTemp)) {
440+
try {
441+
yield io.rmRF(credentialsPath);
442+
}
443+
catch (err) {
444+
core.debug(`Failed to remove credentials config '${credentialsPath}': ${err}`);
445+
}
446+
}
447+
}
416448
});
417449
}
418450
removeGitConfig(configKey_1) {
@@ -430,6 +462,49 @@ class GitAuthHelper {
430462
`sh -c "git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :"`, true);
431463
});
432464
}
465+
/**
466+
* Removes includeIf entries that point to git-credentials-*.config files.
467+
* This handles cleanup of credentials configured by newer versions of the action.
468+
* @param configPath Optional path to a specific git config file to operate on
469+
* @returns Array of unique credentials config file paths that were found and removed
470+
*/
471+
removeIncludeIfCredentials(configPath) {
472+
return __awaiter(this, void 0, void 0, function* () {
473+
const credentialsPaths = new Set();
474+
try {
475+
// Get all includeIf.gitdir keys
476+
const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir:', false, // globalConfig?
477+
configPath);
478+
for (const key of keys) {
479+
// Get all values for this key
480+
const values = yield this.git.tryGetConfigValues(key, false, // globalConfig?
481+
configPath);
482+
if (values.length > 0) {
483+
// Remove only values that match git-credentials-<uuid>.config pattern
484+
for (const value of values) {
485+
if (this.testCredentialsConfigPath(value)) {
486+
credentialsPaths.add(value);
487+
yield this.git.tryConfigUnsetValue(key, value, false, configPath);
488+
}
489+
}
490+
}
491+
}
492+
}
493+
catch (err) {
494+
// Ignore errors - this is cleanup code
495+
core.debug(`Error during includeIf cleanup${configPath ? ` for ${configPath}` : ''}: ${err}`);
496+
}
497+
return Array.from(credentialsPaths);
498+
});
499+
}
500+
/**
501+
* Tests if a path matches the git-credentials-*.config pattern used by newer versions.
502+
* @param path The path to test
503+
* @returns True if the path matches the credentials config pattern
504+
*/
505+
testCredentialsConfigPath(path) {
506+
return /git-credentials-[0-9a-f-]+\.config$/i.test(path);
507+
}
433508
}
434509

435510

@@ -706,6 +781,16 @@ class GitCommandManager {
706781
throw new Error('Unexpected output when retrieving default branch');
707782
});
708783
}
784+
getSubmoduleConfigPaths(recursive) {
785+
return __awaiter(this, void 0, void 0, function* () {
786+
// Get submodule config file paths.
787+
// Use `--show-origin` to get the config file path for each submodule.
788+
const output = yield this.submoduleForeach(`git config --local --show-origin --name-only --get-regexp remote.origin.url`, recursive);
789+
// Extract config file paths from the output (lines starting with "file:").
790+
const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || [];
791+
return configPaths;
792+
});
793+
}
709794
getWorkingDirectory() {
710795
return this.workingDirectory;
711796
}
@@ -836,6 +921,20 @@ class GitCommandManager {
836921
return output.exitCode === 0;
837922
});
838923
}
924+
tryConfigUnsetValue(configKey, configValue, globalConfig, configFile) {
925+
return __awaiter(this, void 0, void 0, function* () {
926+
const args = ['config'];
927+
if (configFile) {
928+
args.push('--file', configFile);
929+
}
930+
else {
931+
args.push(globalConfig ? '--global' : '--local');
932+
}
933+
args.push('--unset', configKey, configValue);
934+
const output = yield this.execGit(args, true);
935+
return output.exitCode === 0;
936+
});
937+
}
839938
tryDisableAutomaticGarbageCollection() {
840939
return __awaiter(this, void 0, void 0, function* () {
841940
const output = yield this.execGit(['config', '--local', 'gc.auto', '0'], true);
@@ -855,6 +954,46 @@ class GitCommandManager {
855954
return stdout;
856955
});
857956
}
957+
tryGetConfigValues(configKey, globalConfig, configFile) {
958+
return __awaiter(this, void 0, void 0, function* () {
959+
const args = ['config'];
960+
if (configFile) {
961+
args.push('--file', configFile);
962+
}
963+
else {
964+
args.push(globalConfig ? '--global' : '--local');
965+
}
966+
args.push('--get-all', configKey);
967+
const output = yield this.execGit(args, true);
968+
if (output.exitCode !== 0) {
969+
return [];
970+
}
971+
return output.stdout
972+
.trim()
973+
.split('\n')
974+
.filter(value => value.trim());
975+
});
976+
}
977+
tryGetConfigKeys(pattern, globalConfig, configFile) {
978+
return __awaiter(this, void 0, void 0, function* () {
979+
const args = ['config'];
980+
if (configFile) {
981+
args.push('--file', configFile);
982+
}
983+
else {
984+
args.push(globalConfig ? '--global' : '--local');
985+
}
986+
args.push('--name-only', '--get-regexp', pattern);
987+
const output = yield this.execGit(args, true);
988+
if (output.exitCode !== 0) {
989+
return [];
990+
}
991+
return output.stdout
992+
.trim()
993+
.split('\n')
994+
.filter(key => key.trim());
995+
});
996+
}
858997
tryReset() {
859998
return __awaiter(this, void 0, void 0, function* () {
860999
const output = yield this.execGit(['reset', '--hard', 'HEAD'], true);

src/git-auth-helper.ts

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,46 @@ 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+
356+
// Collect credentials config paths that need to be removed
357+
const credentialsPaths = new Set<string>()
358+
359+
// Remove includeIf entries that point to git-credentials-*.config files
360+
const mainCredentialsPaths = await this.removeIncludeIfCredentials()
361+
mainCredentialsPaths.forEach(path => credentialsPaths.add(path))
362+
363+
// Remove submodule includeIf entries that point to git-credentials-*.config files
364+
try {
365+
const submoduleConfigPaths = await this.git.getSubmoduleConfigPaths(true)
366+
for (const configPath of submoduleConfigPaths) {
367+
const submoduleCredentialsPaths =
368+
await this.removeIncludeIfCredentials(configPath)
369+
submoduleCredentialsPaths.forEach(path => credentialsPaths.add(path))
370+
}
371+
} catch (err) {
372+
core.debug(`Unable to get submodule config paths: ${err}`)
373+
}
374+
375+
// Remove credentials config files
376+
for (const credentialsPath of credentialsPaths) {
377+
// Only remove credentials config files if they are under RUNNER_TEMP
378+
const runnerTemp = process.env['RUNNER_TEMP']
379+
if (runnerTemp && credentialsPath.startsWith(runnerTemp)) {
380+
try {
381+
await io.rmRF(credentialsPath)
382+
} catch (err) {
383+
core.debug(
384+
`Failed to remove credentials config '${credentialsPath}': ${err}`
385+
)
386+
}
387+
}
388+
}
351389
}
352390

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

0 commit comments

Comments
 (0)