feat: adopt checkout@v6 auth pattern using credentials files

Replace the workaround that hid/restored checkout@v6 credential files
with a proper implementation that aligns with actions/checkout@v6's
new authentication approach.

Changes:
- Store credentials in separate config file in RUNNER_TEMP with UUID
- Use git's includeIf.gitdir mechanism to conditionally include credentials
- Support both host and Docker container paths for credential resolution
- Add worktree path support for git worktrees
- Maintain backwards compatibility with checkout@v4/v5 old-style auth

GitCommandManager:
- Add configFile parameter to config() method
- Add tryConfigUnsetValue() for key-value specific unset
- Add tryGetConfigValues() for multi-value config keys
- Add tryGetConfigKeys() for regex pattern matching config keys

GitConfigHelper:
- Remove hacky hide/unhide credential file approach
- Add getCredentialsConfigPath() for UUID-based credential file paths
- Add configureIncludeIf() for setting up includeIf entries
- Add removeIncludeIfCredentials() for cleanup
- Retain setExtraheaderConfig() for restoring old-style persisted auth
This commit is contained in:
Peter Evans 2026-01-21 16:58:41 +00:00
parent c0f553fe54
commit 4924300074
4 changed files with 546 additions and 151 deletions

View file

@ -96,9 +96,15 @@ export class GitCommandManager {
configKey: string,
configValue: string,
globalConfig?: boolean,
add?: boolean
add?: boolean,
configFile?: string
): Promise<void> {
const args: string[] = ['config', globalConfig ? '--global' : '--local']
const args: string[] = ['config']
if (configFile) {
args.push('--file', configFile)
} else {
args.push(globalConfig ? '--global' : '--local')
}
if (add) {
args.push('--add')
}
@ -350,6 +356,67 @@ export class GitCommandManager {
return output.exitCode === 0
}
async tryConfigUnsetValue(
configKey: string,
configValue: string,
globalConfig?: boolean,
configFile?: string
): Promise<boolean> {
const args = ['config']
if (configFile) {
args.push('--file', configFile)
} else {
args.push(globalConfig ? '--global' : '--local')
}
args.push('--unset', configKey, configValue)
const output = await this.exec(args, {allowAllExitCodes: true})
return output.exitCode === 0
}
async tryGetConfigValues(
configKey: string,
globalConfig?: boolean,
configFile?: string
): Promise<string[]> {
const args = ['config']
if (configFile) {
args.push('--file', configFile)
} else {
args.push(globalConfig ? '--global' : '--local')
}
args.push('--get-all', configKey)
const output = await this.exec(args, {allowAllExitCodes: true})
if (output.exitCode !== 0) {
return []
}
return output.stdout
.trim()
.split('\n')
.filter(value => value.trim())
}
async tryGetConfigKeys(
pattern: string,
globalConfig?: boolean,
configFile?: string
): Promise<string[]> {
const args = ['config']
if (configFile) {
args.push('--file', configFile)
} else {
args.push(globalConfig ? '--global' : '--local')
}
args.push('--name-only', '--get-regexp', pattern)
const output = await this.exec(args, {allowAllExitCodes: true})
if (output.exitCode !== 0) {
return []
}
return output.stdout
.trim()
.split('\n')
.filter(key => key.trim())
}
async tryGetRemoteUrl(): Promise<string> {
const output = await this.exec(
['config', '--local', '--get', 'remote.origin.url'],