From 0f2eb6b1463fa88ed31f347c52112235aaece31a Mon Sep 17 00:00:00 2001 From: eric sciple Date: Tue, 14 Oct 2025 23:15:53 +0000 Subject: [PATCH] Split removeGitConfig, improve comments, fix tests, and set GITHUB_WORKSPACE in tests --- __test__/git-auth-helper.test.ts | 11 ++++- dist/index.js | 73 ++++++++++++++-------------- src/git-auth-helper.ts | 81 +++++++++++++++----------------- 3 files changed, 85 insertions(+), 80 deletions(-) diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts index c08fb2d..15c0736 100644 --- a/__test__/git-auth-helper.test.ts +++ b/__test__/git-auth-helper.test.ts @@ -595,11 +595,14 @@ describe('git-auth-helper tests', () => { await authHelper.configureSubmoduleAuth() // Assert + // Should get submodule config paths (1 call) and configure insteadOf (2 calls for two values) expect(mockSubmoduleForeach).toHaveBeenCalledTimes(4) expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( /unset-all.*insteadOf/ ) - expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/) + expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch( + /show-origin.*remote\.origin\.url/ + ) expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch( /url.*insteadOf.*git@github.com:/ ) @@ -634,11 +637,14 @@ describe('git-auth-helper tests', () => { await authHelper.configureSubmoduleAuth() // Assert + // Should get submodule config paths (1 call) and configure sshCommand (1 call) expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3) expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( /unset-all.*insteadOf/ ) - expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/) + expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch( + /show-origin.*remote\.origin\.url/ + ) expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/core\.sshCommand/) } ) @@ -776,6 +782,7 @@ async function setup(testName: string): Promise { await fs.promises.mkdir(tempHomedir, {recursive: true}) process.env['RUNNER_TEMP'] = runnerTemp process.env['HOME'] = tempHomedir + process.env['GITHUB_WORKSPACE'] = workspace // Create git config globalGitConfigPath = path.join(tempHomedir, '.gitconfig') diff --git a/dist/index.js b/dist/index.js index 36fb2d2..d95cb13 100644 --- a/dist/index.js +++ b/dist/index.js @@ -163,7 +163,7 @@ class GitAuthHelper { this.sshKnownHostsPath = ''; this.temporaryHomePath = ''; this.credentialsConfigPath = ''; // Path to separate credentials config file in RUNNER_TEMP - this.credentialsIncludeKeys = []; // Track includeIf/include config keys for cleanup + this.credentialsIncludeKeys = []; // Track includeIf config keys for cleanup this.git = gitCommandManager; this.settings = gitSourceSettings || {}; // Token auth header @@ -268,18 +268,19 @@ class GitAuthHelper { configureSubmoduleAuth() { return __awaiter(this, void 0, void 0, function* () { // Remove possible previous HTTPS instead of SSH - yield this.removeGitConfig(this.insteadOfKey, true); + yield this.removeSubmoduleGitConfig(this.insteadOfKey); if (this.settings.persistCredentials) { - // Use the same credentials config file created for the main repo + // Credentials config path const credentialsConfigPath = yield this.getCredentialsConfigPath(); + // Container credentials config path + const containerCredentialsPath = path.posix.join('/github/runner_temp', path.basename(credentialsConfigPath)); + // Container repo path + const workingDirectory = this.git.getWorkingDirectory(); const githubWorkspace = process.env['GITHUB_WORKSPACE']; assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined'); - const containerCredentialsPath = path.posix.join('/github/runner_temp', path.basename(credentialsConfigPath)); - // Calculate container git directory base path - const workingDirectory = this.git.getWorkingDirectory(); let relativePath = path.relative(githubWorkspace, workingDirectory); relativePath = relativePath.replace(/\\/g, '/'); - const containerWorkspaceBase = path.posix.join('/github/workspace', relativePath); + const containerRepoPath = path.posix.join('/github/workspace', relativePath); // Get submodule config file paths. // Use `--show-origin` to get the config file path for each submodule. const output = yield this.git.submoduleForeach(`git config --local --show-origin --name-only --get-regexp remote.origin.url`, this.settings.nestedSubmodules); @@ -288,18 +289,16 @@ class GitAuthHelper { // For each submodule, configure includeIf entries pointing to the shared credentials file. // Configure both host and container paths to support Docker container actions. for (const configPath of configPaths) { - // Get the submodule path from its config file path. + // Submodule path const submodulePath = path.dirname(path.dirname(configPath)); - // Configure host path includeIf. - // Use forward slashes for git config, even on Windows. + // Configure host includeIf let submoduleGitDir = path.join(submodulePath, '.git'); - submoduleGitDir = submoduleGitDir.replace(/\\/g, '/'); + submoduleGitDir = submoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows yield this.git.config(`includeIf.gitdir:${submoduleGitDir}.path`, credentialsConfigPath, false, false, configPath); - // Configure container path includeIf. - // Use forward slashes for git config, even on Windows. + // Configure container includeIf let submoduleRelativePath = path.relative(workingDirectory, submodulePath); - submoduleRelativePath = submoduleRelativePath.replace(/\\/g, '/'); - const containerSubmoduleGitDir = path.posix.join(containerWorkspaceBase, submoduleRelativePath, '.git'); + submoduleRelativePath = submoduleRelativePath.replace(/\\/g, '/'); // Use forward slashes, even on Windows + const containerSubmoduleGitDir = path.posix.join(containerRepoPath, submoduleRelativePath, '.git'); yield this.git.config(`includeIf.gitdir:${containerSubmoduleGitDir}.path`, containerCredentialsPath, false, false, configPath); } if (this.settings.sshKey) { @@ -404,25 +403,23 @@ class GitAuthHelper { yield this.git.config('include.path', credentialsConfigPath, true); } else { - // For local config, use includeIf.gitdir to match the .git directory. - // Configure for both host and container paths to support Docker container actions. + // Host git directory let gitDir = path.join(this.git.getWorkingDirectory(), '.git'); - // Use forward slashes for git config, even on Windows - gitDir = gitDir.replace(/\\/g, '/'); + gitDir = gitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows + // Configure host includeIf const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`; yield this.git.config(hostIncludeKey, credentialsConfigPath); this.credentialsIncludeKeys.push(hostIncludeKey); - // Configure for container scenario where paths are mapped to fixed locations + // Container git directory const githubWorkspace = process.env['GITHUB_WORKSPACE']; assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined'); - // Calculate the relative path of the working directory from GITHUB_WORKSPACE const workingDirectory = this.git.getWorkingDirectory(); let relativePath = path.relative(githubWorkspace, workingDirectory); - // Container paths: GITHUB_WORKSPACE -> /github/workspace, RUNNER_TEMP -> /github/runner_temp - // Use forward slashes for git config - relativePath = relativePath.replace(/\\/g, '/'); + relativePath = relativePath.replace(/\\/g, '/'); // Use forward slashes, even on Windows const containerGitDir = path.posix.join('/github/workspace', relativePath, '.git'); + // Container credentials config path const containerCredentialsPath = path.posix.join('/github/runner_temp', path.basename(credentialsConfigPath)); + // Configure container includeIf const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`; yield this.git.config(containerIncludeKey, containerCredentialsPath); this.credentialsIncludeKeys.push(containerIncludeKey); @@ -469,19 +466,21 @@ class GitAuthHelper { } // SSH command yield this.removeGitConfig(SSH_COMMAND_KEY); + yield this.removeSubmoduleGitConfig(SSH_COMMAND_KEY); }); } removeToken() { return __awaiter(this, void 0, void 0, function* () { var _a; - // HTTP extra header + // Remove HTTP extra header yield this.removeGitConfig(this.tokenConfigKey); - // Remove include/includeIf config entries + yield this.removeSubmoduleGitConfig(this.tokenConfigKey); + // Remove includeIf for (const includeKey of this.credentialsIncludeKeys) { yield this.removeGitConfig(includeKey); } this.credentialsIncludeKeys = []; - // Remove includeIf entries from submodules + // 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 if (this.credentialsConfigPath) { @@ -495,18 +494,20 @@ class GitAuthHelper { } }); } - removeGitConfig(configKey_1) { - return __awaiter(this, arguments, void 0, function* (configKey, submoduleOnly = false) { - if (!submoduleOnly) { - if ((yield this.git.configExists(configKey)) && - !(yield this.git.tryConfigUnset(configKey))) { - // Load the config contents - core.warning(`Failed to remove '${configKey}' from the git config`); - } + removeGitConfig(configKey) { + return __awaiter(this, void 0, void 0, function* () { + if ((yield this.git.configExists(configKey)) && + !(yield this.git.tryConfigUnset(configKey))) { + // Load the config contents + core.warning(`Failed to remove '${configKey}' from the git config`); } + }); + } + removeSubmoduleGitConfig(configKey) { + return __awaiter(this, void 0, void 0, function* () { const pattern = regexpHelper.escape(configKey); yield this.git.submoduleForeach( - // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline + // Wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline. `sh -c "git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :"`, true); }); } diff --git a/src/git-auth-helper.ts b/src/git-auth-helper.ts index 4abc64e..6cf4ab1 100644 --- a/src/git-auth-helper.ts +++ b/src/git-auth-helper.ts @@ -44,7 +44,7 @@ class GitAuthHelper { private sshKnownHostsPath = '' private temporaryHomePath = '' private credentialsConfigPath = '' // Path to separate credentials config file in RUNNER_TEMP - private credentialsIncludeKeys: string[] = [] // Track includeIf/include config keys for cleanup + private credentialsIncludeKeys: string[] = [] // Track includeIf config keys for cleanup constructor( gitCommandManager: IGitCommandManager, @@ -168,24 +168,25 @@ class GitAuthHelper { async configureSubmoduleAuth(): Promise { // Remove possible previous HTTPS instead of SSH - await this.removeGitConfig(this.insteadOfKey, true) + await this.removeSubmoduleGitConfig(this.insteadOfKey) if (this.settings.persistCredentials) { - // Use the same credentials config file created for the main repo + // Credentials config path const credentialsConfigPath = await this.getCredentialsConfigPath() - const githubWorkspace = process.env['GITHUB_WORKSPACE'] - assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined') + // Container credentials config path const containerCredentialsPath = path.posix.join( '/github/runner_temp', path.basename(credentialsConfigPath) ) - // Calculate container git directory base path + // Container repo path const workingDirectory = this.git.getWorkingDirectory() + const githubWorkspace = process.env['GITHUB_WORKSPACE'] + assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined') let relativePath = path.relative(githubWorkspace, workingDirectory) relativePath = relativePath.replace(/\\/g, '/') - const containerWorkspaceBase = path.posix.join( + const containerRepoPath = path.posix.join( '/github/workspace', relativePath ) @@ -204,12 +205,12 @@ class GitAuthHelper { // For each submodule, configure includeIf entries pointing to the shared credentials file. // Configure both host and container paths to support Docker container actions. for (const configPath of configPaths) { - // Get the submodule path from its config file path. + // Submodule path const submodulePath = path.dirname(path.dirname(configPath)) - // Configure host path includeIf. - // Use forward slashes for git config, even on Windows. + + // Configure host includeIf let submoduleGitDir = path.join(submodulePath, '.git') - submoduleGitDir = submoduleGitDir.replace(/\\/g, '/') + submoduleGitDir = submoduleGitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows await this.git.config( `includeIf.gitdir:${submoduleGitDir}.path`, credentialsConfigPath, @@ -218,15 +219,14 @@ class GitAuthHelper { configPath ) - // Configure container path includeIf. - // Use forward slashes for git config, even on Windows. + // Configure container includeIf let submoduleRelativePath = path.relative( workingDirectory, submodulePath ) - submoduleRelativePath = submoduleRelativePath.replace(/\\/g, '/') + submoduleRelativePath = submoduleRelativePath.replace(/\\/g, '/') // Use forward slashes, even on Windows const containerSubmoduleGitDir = path.posix.join( - containerWorkspaceBase, + containerRepoPath, submoduleRelativePath, '.git' ) @@ -365,36 +365,34 @@ class GitAuthHelper { // Global config file is temporary await this.git.config('include.path', credentialsConfigPath, true) } else { - // For local config, use includeIf.gitdir to match the .git directory. - // Configure for both host and container paths to support Docker container actions. + // Host git directory let gitDir = path.join(this.git.getWorkingDirectory(), '.git') - // Use forward slashes for git config, even on Windows - gitDir = gitDir.replace(/\\/g, '/') + gitDir = gitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows + + // Configure host includeIf const hostIncludeKey = `includeIf.gitdir:${gitDir}.path` await this.git.config(hostIncludeKey, credentialsConfigPath) this.credentialsIncludeKeys.push(hostIncludeKey) - // Configure for container scenario where paths are mapped to fixed locations + // Container git directory const githubWorkspace = process.env['GITHUB_WORKSPACE'] assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined') - - // Calculate the relative path of the working directory from GITHUB_WORKSPACE const workingDirectory = this.git.getWorkingDirectory() let relativePath = path.relative(githubWorkspace, workingDirectory) - - // Container paths: GITHUB_WORKSPACE -> /github/workspace, RUNNER_TEMP -> /github/runner_temp - // Use forward slashes for git config - relativePath = relativePath.replace(/\\/g, '/') + relativePath = relativePath.replace(/\\/g, '/') // Use forward slashes, even on Windows const containerGitDir = path.posix.join( '/github/workspace', relativePath, '.git' ) + + // Container credentials config path const containerCredentialsPath = path.posix.join( '/github/runner_temp', path.basename(credentialsConfigPath) ) + // Configure container includeIf const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path` await this.git.config(containerIncludeKey, containerCredentialsPath) this.credentialsIncludeKeys.push(containerIncludeKey) @@ -444,19 +442,21 @@ class GitAuthHelper { // SSH command await this.removeGitConfig(SSH_COMMAND_KEY) + await this.removeSubmoduleGitConfig(SSH_COMMAND_KEY) } private async removeToken(): Promise { - // HTTP extra header + // Remove HTTP extra header await this.removeGitConfig(this.tokenConfigKey) + await this.removeSubmoduleGitConfig(this.tokenConfigKey) - // Remove include/includeIf config entries + // Remove includeIf for (const includeKey of this.credentialsIncludeKeys) { await this.removeGitConfig(includeKey) } this.credentialsIncludeKeys = [] - // Remove includeIf entries from submodules + // Remove submodule includeIf await this.git.submoduleForeach( `sh -c "git config --local --get-regexp '^includeIf\\.' && git config --local --remove-section includeIf || :"`, true @@ -475,23 +475,20 @@ class GitAuthHelper { } } - private async removeGitConfig( - configKey: string, - submoduleOnly: boolean = false - ): Promise { - if (!submoduleOnly) { - if ( - (await this.git.configExists(configKey)) && - !(await this.git.tryConfigUnset(configKey)) - ) { - // Load the config contents - core.warning(`Failed to remove '${configKey}' from the git config`) - } + private async removeGitConfig(configKey: string): Promise { + if ( + (await this.git.configExists(configKey)) && + !(await this.git.tryConfigUnset(configKey)) + ) { + // Load the config contents + core.warning(`Failed to remove '${configKey}' from the git config`) } + } + private async removeSubmoduleGitConfig(configKey: string): Promise { const pattern = regexpHelper.escape(configKey) await this.git.submoduleForeach( - // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline + // Wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline. `sh -c "git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :"`, true )