From e4894fca20d13f959e14bd70dcb88446d867a802 Mon Sep 17 00:00:00 2001 From: eric sciple Date: Thu, 16 Oct 2025 21:58:52 +0000 Subject: [PATCH] . --- __test__/git-auth-helper.test.ts | 45 +++++++++++++++++ __test__/git-directory-helper.test.ts | 3 ++ __test__/verify-submodules-recursive.sh | 2 +- __test__/verify-submodules-true.sh | 2 +- dist/index.js | 67 +++++++++++++++++++++++-- src/git-auth-helper.ts | 26 ++++++++-- src/git-command-manager.ts | 64 +++++++++++++++++++++++ 7 files changed, 199 insertions(+), 10 deletions(-) diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts index 15c0736..f8700ee 100644 --- a/__test__/git-auth-helper.test.ts +++ b/__test__/git-auth-helper.test.ts @@ -872,8 +872,53 @@ async function setup(testName: string): Promise { return true } ), + tryConfigUnsetValue: jest.fn( + async (key: string, value: string, globalConfig?: boolean): Promise => { + const configPath = globalConfig + ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') + : localGitConfigPath + let content = await fs.promises.readFile(configPath) + let lines = content + .toString() + .split('\n') + .filter(x => x) + .filter(x => !(x.startsWith(key) && x.includes(value))) + await fs.promises.writeFile(configPath, lines.join('\n')) + return true + } + ), tryDisableAutomaticGarbageCollection: jest.fn(), tryGetFetchUrl: jest.fn(), + tryGetConfigValues: jest.fn( + async (key: string, globalConfig?: boolean): Promise => { + const configPath = globalConfig + ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') + : localGitConfigPath + const content = await fs.promises.readFile(configPath) + const lines = content + .toString() + .split('\n') + .filter(x => x && x.startsWith(key)) + .map(x => x.substring(key.length).trim()) + return lines + } + ), + tryGetConfigKeys: jest.fn( + async (pattern: string, globalConfig?: boolean): Promise => { + const configPath = globalConfig + ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') + : localGitConfigPath + const content = await fs.promises.readFile(configPath) + const lines = content + .toString() + .split('\n') + .filter(x => x) + const keys = lines + .filter(x => new RegExp(pattern).test(x.split(' ')[0])) + .map(x => x.split(' ')[0]) + return [...new Set(keys)] // Remove duplicates + } + ), tryReset: jest.fn(), version: jest.fn() } diff --git a/__test__/git-directory-helper.test.ts b/__test__/git-directory-helper.test.ts index 22e9ae6..d728e28 100644 --- a/__test__/git-directory-helper.test.ts +++ b/__test__/git-directory-helper.test.ts @@ -493,12 +493,15 @@ async function setup(testName: string): Promise { return true }), tryConfigUnset: jest.fn(), + tryConfigUnsetValue: jest.fn(), tryDisableAutomaticGarbageCollection: jest.fn(), tryGetFetchUrl: jest.fn(async () => { // Sanity check - this function shouldn't be called when the .git directory doesn't exist await fs.promises.stat(path.join(repositoryPath, '.git')) return repositoryUrl }), + tryGetConfigValues: jest.fn(), + tryGetConfigKeys: jest.fn(), tryReset: jest.fn(async () => { return true }), diff --git a/__test__/verify-submodules-recursive.sh b/__test__/verify-submodules-recursive.sh index bdb803c..1b68f9b 100755 --- a/__test__/verify-submodules-recursive.sh +++ b/__test__/verify-submodules-recursive.sh @@ -17,7 +17,7 @@ fi echo "Testing persisted credential" pushd ./submodules-recursive/submodule-level-1/submodule-level-2 -git config --local --name-only --get-regexp '^includeIf\.' && git fetch +git config --local --name-only --get-regexp http.+extraheader && git fetch if [ "$?" != "0" ]; then echo "Failed to validate persisted credential" popd diff --git a/__test__/verify-submodules-true.sh b/__test__/verify-submodules-true.sh index d2cd831..43769fe 100755 --- a/__test__/verify-submodules-true.sh +++ b/__test__/verify-submodules-true.sh @@ -17,7 +17,7 @@ fi echo "Testing persisted credential" pushd ./submodules-true/submodule-level-1 -git config --local --name-only --get-regexp '^includeIf\.' && git fetch +git config --local --name-only --get-regexp http.+extraheader && git fetch if [ "$?" != "0" ]; then echo "Failed to validate persisted credential" popd diff --git a/dist/index.js b/dist/index.js index 7c0a591..df77d86 100644 --- a/dist/index.js +++ b/dist/index.js @@ -474,11 +474,29 @@ class GitAuthHelper { // Remove HTTP extra header yield this.removeGitConfig(this.tokenConfigKey); yield this.removeSubmoduleGitConfig(this.tokenConfigKey); - // Remove includeIf - for (const includeKey of this.credentialsIncludeKeys) { - yield this.removeGitConfig(includeKey); + // Remove includeIf entries that point to git-credentials-*.config files + // This is more aggressive than tracking keys, but necessary since cleanup + // runs in a post-step where this.credentialsIncludeKeys is empty + try { + // Get all includeIf.gitdir keys + const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir:'); + for (const key of keys) { + // Get all values for this key + const values = yield this.git.tryGetConfigValues(key); + if (values.length > 0) { + // Remove only values that match git-credentials-.config pattern + for (const value of values) { + if (/git-credentials-[0-9a-f-]+\.config$/i.test(value)) { + yield this.git.tryConfigUnsetValue(key, value); + } + } + } + } + } + catch (err) { + // Ignore errors - this is cleanup code + core.debug(`Error during includeIf cleanup: ${err}`); } - this.credentialsIncludeKeys = []; // Remove submodule includeIf yield this.git.submoduleForeach(`sh -c "git config --local --get-regexp '^includeIf\\.' && git config --local --remove-section includeIf || :"`, true); // Remove credentials config file @@ -922,6 +940,18 @@ class GitCommandManager { return output.exitCode === 0; }); } + tryConfigUnsetValue(configKey, configValue, globalConfig) { + return __awaiter(this, void 0, void 0, function* () { + const output = yield this.execGit([ + 'config', + globalConfig ? '--global' : '--local', + '--unset', + configKey, + configValue + ], true); + return output.exitCode === 0; + }); + } tryDisableAutomaticGarbageCollection() { return __awaiter(this, void 0, void 0, function* () { const output = yield this.execGit(['config', '--local', 'gc.auto', '0'], true); @@ -941,6 +971,35 @@ class GitCommandManager { return stdout; }); } + tryGetConfigValues(configKey, globalConfig) { + return __awaiter(this, void 0, void 0, function* () { + const output = yield this.execGit([ + 'config', + globalConfig ? '--global' : '--local', + '--get-all', + configKey + ], true); + if (output.exitCode !== 0) { + return []; + } + return output.stdout.trim().split('\n').filter(value => value.trim()); + }); + } + tryGetConfigKeys(pattern, globalConfig) { + return __awaiter(this, void 0, void 0, function* () { + const output = yield this.execGit([ + 'config', + globalConfig ? '--global' : '--local', + '--name-only', + '--get-regexp', + pattern + ], true); + if (output.exitCode !== 0) { + return []; + } + return output.stdout.trim().split('\n').filter(key => key.trim()); + }); + } tryReset() { return __awaiter(this, void 0, void 0, function* () { const output = yield this.execGit(['reset', '--hard', 'HEAD'], true); diff --git a/src/git-auth-helper.ts b/src/git-auth-helper.ts index 60cc187..a529041 100644 --- a/src/git-auth-helper.ts +++ b/src/git-auth-helper.ts @@ -448,11 +448,29 @@ class GitAuthHelper { await this.removeGitConfig(this.tokenConfigKey) await this.removeSubmoduleGitConfig(this.tokenConfigKey) - // Remove includeIf - for (const includeKey of this.credentialsIncludeKeys) { - await this.removeGitConfig(includeKey) + // Remove includeIf entries that point to git-credentials-*.config files + // This is more aggressive than tracking keys, but necessary since cleanup + // runs in a post-step where this.credentialsIncludeKeys is empty + try { + // Get all includeIf.gitdir keys + const keys = await this.git.tryGetConfigKeys('^includeIf\\.gitdir:') + + for (const key of keys) { + // Get all values for this key + const values = await this.git.tryGetConfigValues(key) + if (values.length > 0) { + // Remove only values that match git-credentials-.config pattern + for (const value of values) { + if (/git-credentials-[0-9a-f-]+\.config$/i.test(value)) { + await this.git.tryConfigUnsetValue(key, value) + } + } + } + } + } catch (err) { + // Ignore errors - this is cleanup code + core.debug(`Error during includeIf cleanup: ${err}`) } - this.credentialsIncludeKeys = [] // Remove submodule includeIf await this.git.submoduleForeach( diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index 0dfb11c..ed3220b 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -60,8 +60,11 @@ export interface IGitCommandManager { tagExists(pattern: string): Promise tryClean(): Promise tryConfigUnset(configKey: string, globalConfig?: boolean): Promise + tryConfigUnsetValue(configKey: string, configValue: string, globalConfig?: boolean): Promise tryDisableAutomaticGarbageCollection(): Promise tryGetFetchUrl(): Promise + tryGetConfigValues(configKey: string, globalConfig?: boolean): Promise + tryGetConfigKeys(pattern: string, globalConfig?: boolean): Promise tryReset(): Promise version(): Promise } @@ -462,6 +465,24 @@ class GitCommandManager { return output.exitCode === 0 } + async tryConfigUnsetValue( + configKey: string, + configValue: string, + globalConfig?: boolean + ): Promise { + const output = await this.execGit( + [ + 'config', + globalConfig ? '--global' : '--local', + '--unset', + configKey, + configValue + ], + true + ) + return output.exitCode === 0 + } + async tryDisableAutomaticGarbageCollection(): Promise { const output = await this.execGit( ['config', '--local', 'gc.auto', '0'], @@ -488,6 +509,49 @@ class GitCommandManager { return stdout } + async tryGetConfigValues( + configKey: string, + globalConfig?: boolean + ): Promise { + const output = await this.execGit( + [ + 'config', + globalConfig ? '--global' : '--local', + '--get-all', + configKey + ], + true + ) + + if (output.exitCode !== 0) { + return [] + } + + return output.stdout.trim().split('\n').filter(value => value.trim()) + } + + async tryGetConfigKeys( + pattern: string, + globalConfig?: boolean + ): Promise { + const output = await this.execGit( + [ + 'config', + globalConfig ? '--global' : '--local', + '--name-only', + '--get-regexp', + pattern + ], + true + ) + + if (output.exitCode !== 0) { + return [] + } + + return output.stdout.trim().split('\n').filter(key => key.trim()) + } + async tryReset(): Promise { const output = await this.execGit(['reset', '--hard', 'HEAD'], true) return output.exitCode === 0