mirror of
https://gitea.com/actions/checkout.git
synced 2025-11-13 18:16:21 +08:00
Compare commits
1 Commits
317d15a1e8
...
users/eric
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec6a68f81a |
11
.github/workflows/test.yml
vendored
11
.github/workflows/test.yml
vendored
@@ -299,18 +299,15 @@ jobs:
|
|||||||
test-output:
|
test-output:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
# Download the action at the current ref
|
# Clone this repo
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4.1.6
|
uses: actions/checkout@v4.1.6
|
||||||
with:
|
|
||||||
path: actions-checkout
|
|
||||||
|
|
||||||
# Basic checkout using git
|
# Basic checkout using git
|
||||||
- name: Checkout basic
|
- name: Checkout basic
|
||||||
id: checkout
|
id: checkout
|
||||||
uses: ./actions-checkout
|
uses: ./
|
||||||
with:
|
with:
|
||||||
path: cloned-using-local-action
|
|
||||||
ref: test-data/v2/basic
|
ref: test-data/v2/basic
|
||||||
|
|
||||||
# Verify output
|
# Verify output
|
||||||
@@ -328,3 +325,7 @@ jobs:
|
|||||||
echo "Expected commit to be 82f71901cf8c021332310dcc8cdba84c4193ff5d"
|
echo "Expected commit to be 82f71901cf8c021332310dcc8cdba84c4193ff5d"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# needed to make checkout post cleanup succeed
|
||||||
|
- name: Fix Checkout
|
||||||
|
uses: actions/checkout@v4.1.6
|
||||||
|
|||||||
@@ -2,11 +2,7 @@
|
|||||||
|
|
||||||
# Checkout V5
|
# Checkout V5
|
||||||
|
|
||||||
## What's new
|
Checkout v5 now supports Node.js 24
|
||||||
|
|
||||||
- Updated to the node24 runtime
|
|
||||||
- This requires a minimum Actions Runner version of [v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) to run.
|
|
||||||
|
|
||||||
|
|
||||||
# Checkout V4
|
# Checkout V4
|
||||||
|
|
||||||
@@ -158,10 +154,9 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/
|
|||||||
# Scenarios
|
# Scenarios
|
||||||
|
|
||||||
- [Checkout V5](#checkout-v5)
|
- [Checkout V5](#checkout-v5)
|
||||||
- [What's new](#whats-new)
|
|
||||||
- [Checkout V4](#checkout-v4)
|
- [Checkout V4](#checkout-v4)
|
||||||
- [Note](#note)
|
- [Note](#note)
|
||||||
- [What's new](#whats-new-1)
|
- [What's new](#whats-new)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Scenarios](#scenarios)
|
- [Scenarios](#scenarios)
|
||||||
- [Fetch only the root files](#fetch-only-the-root-files)
|
- [Fetch only the root files](#fetch-only-the-root-files)
|
||||||
|
|||||||
@@ -86,29 +86,16 @@ describe('git-auth-helper tests', () => {
|
|||||||
// Act
|
// Act
|
||||||
await authHelper.configureAuth()
|
await authHelper.configureAuth()
|
||||||
|
|
||||||
// Assert config - check that .git/config contains includeIf entries
|
// Assert config
|
||||||
const localConfigContent = (
|
const configContent = (
|
||||||
await fs.promises.readFile(localGitConfigPath)
|
await fs.promises.readFile(localGitConfigPath)
|
||||||
).toString()
|
).toString()
|
||||||
expect(
|
|
||||||
localConfigContent.indexOf('includeIf.gitdir:')
|
|
||||||
).toBeGreaterThanOrEqual(0)
|
|
||||||
|
|
||||||
// Assert credentials config file contains the actual credentials
|
|
||||||
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
|
||||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
|
||||||
)
|
|
||||||
expect(credentialsFiles.length).toBe(1)
|
|
||||||
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
|
|
||||||
const credentialsContent = (
|
|
||||||
await fs.promises.readFile(credentialsConfigPath)
|
|
||||||
).toString()
|
|
||||||
const basicCredential = Buffer.from(
|
const basicCredential = Buffer.from(
|
||||||
`x-access-token:${settings.authToken}`,
|
`x-access-token:${settings.authToken}`,
|
||||||
'utf8'
|
'utf8'
|
||||||
).toString('base64')
|
).toString('base64')
|
||||||
expect(
|
expect(
|
||||||
credentialsContent.indexOf(
|
configContent.indexOf(
|
||||||
`http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
`http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
||||||
)
|
)
|
||||||
).toBeGreaterThanOrEqual(0)
|
).toBeGreaterThanOrEqual(0)
|
||||||
@@ -133,7 +120,7 @@ describe('git-auth-helper tests', () => {
|
|||||||
'inject https://github.com as github server url'
|
'inject https://github.com as github server url'
|
||||||
it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => {
|
it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => {
|
||||||
await testAuthHeader(
|
await testAuthHeader(
|
||||||
configureAuth_AcceptsGitHubServerUrlSetToGHEC,
|
configureAuth_AcceptsGitHubServerUrl,
|
||||||
'https://github.com'
|
'https://github.com'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -154,17 +141,12 @@ describe('git-auth-helper tests', () => {
|
|||||||
// Act
|
// Act
|
||||||
await authHelper.configureAuth()
|
await authHelper.configureAuth()
|
||||||
|
|
||||||
// Assert config - check credentials config file (not local .git/config)
|
// Assert config
|
||||||
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
const configContent = (
|
||||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
await fs.promises.readFile(localGitConfigPath)
|
||||||
)
|
|
||||||
expect(credentialsFiles.length).toBe(1)
|
|
||||||
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
|
|
||||||
const credentialsContent = (
|
|
||||||
await fs.promises.readFile(credentialsConfigPath)
|
|
||||||
).toString()
|
).toString()
|
||||||
expect(
|
expect(
|
||||||
credentialsContent.indexOf(
|
configContent.indexOf(
|
||||||
`http.https://github.com/.extraheader AUTHORIZATION`
|
`http.https://github.com/.extraheader AUTHORIZATION`
|
||||||
)
|
)
|
||||||
).toBeGreaterThanOrEqual(0)
|
).toBeGreaterThanOrEqual(0)
|
||||||
@@ -269,16 +251,13 @@ describe('git-auth-helper tests', () => {
|
|||||||
expectedSshCommand
|
expectedSshCommand
|
||||||
)
|
)
|
||||||
|
|
||||||
// Assert git config
|
// Asserty git config
|
||||||
const gitConfigLines = (await fs.promises.readFile(localGitConfigPath))
|
const gitConfigLines = (await fs.promises.readFile(localGitConfigPath))
|
||||||
.toString()
|
.toString()
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.filter(x => x)
|
.filter(x => x)
|
||||||
// Should have includeIf entries pointing to credentials file
|
expect(gitConfigLines).toHaveLength(1)
|
||||||
expect(gitConfigLines.length).toBeGreaterThan(0)
|
expect(gitConfigLines[0]).toMatch(/^http\./)
|
||||||
expect(
|
|
||||||
gitConfigLines.some(line => line.indexOf('includeIf.gitdir:') >= 0)
|
|
||||||
).toBeTruthy()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const configureAuth_setsSshCommandWhenPersistCredentialsTrue =
|
const configureAuth_setsSshCommandWhenPersistCredentialsTrue =
|
||||||
@@ -440,20 +419,8 @@ describe('git-auth-helper tests', () => {
|
|||||||
expect(
|
expect(
|
||||||
configContent.indexOf('value-from-global-config')
|
configContent.indexOf('value-from-global-config')
|
||||||
).toBeGreaterThanOrEqual(0)
|
).toBeGreaterThanOrEqual(0)
|
||||||
// Global config should have include.path pointing to credentials file
|
|
||||||
expect(configContent.indexOf('include.path')).toBeGreaterThanOrEqual(0)
|
|
||||||
|
|
||||||
// Check credentials in the separate config file
|
|
||||||
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
|
||||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
|
||||||
)
|
|
||||||
expect(credentialsFiles.length).toBeGreaterThan(0)
|
|
||||||
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
|
|
||||||
const credentialsContent = (
|
|
||||||
await fs.promises.readFile(credentialsConfigPath)
|
|
||||||
).toString()
|
|
||||||
expect(
|
expect(
|
||||||
credentialsContent.indexOf(
|
configContent.indexOf(
|
||||||
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
||||||
)
|
)
|
||||||
).toBeGreaterThanOrEqual(0)
|
).toBeGreaterThanOrEqual(0)
|
||||||
@@ -496,20 +463,8 @@ describe('git-auth-helper tests', () => {
|
|||||||
const configContent = (
|
const configContent = (
|
||||||
await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
|
await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
|
||||||
).toString()
|
).toString()
|
||||||
// Global config should have include.path pointing to credentials file
|
|
||||||
expect(configContent.indexOf('include.path')).toBeGreaterThanOrEqual(0)
|
|
||||||
|
|
||||||
// Check credentials in the separate config file
|
|
||||||
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
|
||||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
|
||||||
)
|
|
||||||
expect(credentialsFiles.length).toBeGreaterThan(0)
|
|
||||||
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
|
|
||||||
const credentialsContent = (
|
|
||||||
await fs.promises.readFile(credentialsConfigPath)
|
|
||||||
).toString()
|
|
||||||
expect(
|
expect(
|
||||||
credentialsContent.indexOf(
|
configContent.indexOf(
|
||||||
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
||||||
)
|
)
|
||||||
).toBeGreaterThanOrEqual(0)
|
).toBeGreaterThanOrEqual(0)
|
||||||
@@ -595,15 +550,15 @@ describe('git-auth-helper tests', () => {
|
|||||||
await authHelper.configureSubmoduleAuth()
|
await authHelper.configureSubmoduleAuth()
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
// Should configure insteadOf (2 calls for two values)
|
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(4)
|
||||||
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
|
|
||||||
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
|
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
|
||||||
/unset-all.*insteadOf/
|
/unset-all.*insteadOf/
|
||||||
)
|
)
|
||||||
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(
|
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
|
||||||
|
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(
|
||||||
/url.*insteadOf.*git@github.com:/
|
/url.*insteadOf.*git@github.com:/
|
||||||
)
|
)
|
||||||
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(
|
expect(mockSubmoduleForeach.mock.calls[3][0]).toMatch(
|
||||||
/url.*insteadOf.*org-123456@github.com:/
|
/url.*insteadOf.*org-123456@github.com:/
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -634,12 +589,12 @@ describe('git-auth-helper tests', () => {
|
|||||||
await authHelper.configureSubmoduleAuth()
|
await authHelper.configureSubmoduleAuth()
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
// Should configure sshCommand (1 call)
|
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
|
||||||
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(2)
|
|
||||||
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
|
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
|
||||||
/unset-all.*insteadOf/
|
/unset-all.*insteadOf/
|
||||||
)
|
)
|
||||||
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/core\.sshCommand/)
|
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
|
||||||
|
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/core\.sshCommand/)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -705,35 +660,296 @@ describe('git-auth-helper tests', () => {
|
|||||||
await setup(removeAuth_removesToken)
|
await setup(removeAuth_removesToken)
|
||||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||||
await authHelper.configureAuth()
|
await authHelper.configureAuth()
|
||||||
|
let gitConfigContent = (
|
||||||
// Sanity check - verify includeIf entries exist in local config
|
|
||||||
let localConfigContent = (
|
|
||||||
await fs.promises.readFile(localGitConfigPath)
|
await fs.promises.readFile(localGitConfigPath)
|
||||||
).toString()
|
).toString()
|
||||||
expect(
|
expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check
|
||||||
localConfigContent.indexOf('includeIf.gitdir:')
|
|
||||||
).toBeGreaterThanOrEqual(0)
|
|
||||||
|
|
||||||
// Sanity check - verify credentials file exists
|
// Act
|
||||||
let credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
await authHelper.removeAuth()
|
||||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
|
||||||
|
// Assert git config
|
||||||
|
gitConfigContent = (
|
||||||
|
await fs.promises.readFile(localGitConfigPath)
|
||||||
|
).toString()
|
||||||
|
expect(gitConfigContent.indexOf('http.')).toBeLessThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const removeAuth_removesV6StyleCredentials =
|
||||||
|
'removeAuth removes v6 style credentials'
|
||||||
|
it(removeAuth_removesV6StyleCredentials, async () => {
|
||||||
|
// Arrange
|
||||||
|
await setup(removeAuth_removesV6StyleCredentials)
|
||||||
|
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||||
|
await authHelper.configureAuth()
|
||||||
|
|
||||||
|
// Manually create v6-style credentials that would be left by v6
|
||||||
|
const credentialsFileName =
|
||||||
|
'git-credentials-12345678-1234-1234-1234-123456789abc.config'
|
||||||
|
const credentialsFilePath = path.join(runnerTemp, credentialsFileName)
|
||||||
|
const basicCredential = Buffer.from(
|
||||||
|
`x-access-token:${settings.authToken}`,
|
||||||
|
'utf8'
|
||||||
|
).toString('base64')
|
||||||
|
const credentialsContent = `[http "https://github.com/"]\n\textraheader = AUTHORIZATION: basic ${basicCredential}\n`
|
||||||
|
await fs.promises.writeFile(credentialsFilePath, credentialsContent)
|
||||||
|
|
||||||
|
// Add includeIf entries to local git config (simulating v6 configuration)
|
||||||
|
const hostGitDir = path.join(workspace, '.git').replace(/\\/g, '/')
|
||||||
|
await fs.promises.appendFile(
|
||||||
|
localGitConfigPath,
|
||||||
|
`[includeIf "gitdir:${hostGitDir}/"]\n\tpath = ${credentialsFilePath}\n`
|
||||||
|
)
|
||||||
|
await fs.promises.appendFile(
|
||||||
|
localGitConfigPath,
|
||||||
|
`[includeIf "gitdir:/github/workspace/.git/"]\n\tpath = /github/runner_temp/${credentialsFileName}\n`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify v6 style config exists
|
||||||
|
let gitConfigContent = (
|
||||||
|
await fs.promises.readFile(localGitConfigPath)
|
||||||
|
).toString()
|
||||||
|
expect(gitConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(0)
|
||||||
|
expect(
|
||||||
|
gitConfigContent.indexOf(credentialsFilePath)
|
||||||
|
).toBeGreaterThanOrEqual(0)
|
||||||
|
await fs.promises.stat(credentialsFilePath) // Verify file exists
|
||||||
|
|
||||||
|
// Mock the git methods to handle v6 cleanup
|
||||||
|
const mockTryGetConfigKeys = git.tryGetConfigKeys as jest.Mock<any, any>
|
||||||
|
mockTryGetConfigKeys.mockResolvedValue([
|
||||||
|
`includeIf.gitdir:${hostGitDir}/.path`,
|
||||||
|
'includeIf.gitdir:/github/workspace/.git/.path'
|
||||||
|
])
|
||||||
|
|
||||||
|
const mockTryGetConfigValues = git.tryGetConfigValues as jest.Mock<any, any>
|
||||||
|
mockTryGetConfigValues.mockImplementation(async (key: string) => {
|
||||||
|
if (key === `includeIf.gitdir:${hostGitDir}/.path`) {
|
||||||
|
return [credentialsFilePath]
|
||||||
|
}
|
||||||
|
if (key === 'includeIf.gitdir:/github/workspace/.git/.path') {
|
||||||
|
return [`/github/runner_temp/${credentialsFileName}`]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockTryConfigUnsetValue = git.tryConfigUnsetValue as jest.Mock<
|
||||||
|
any,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
mockTryConfigUnsetValue.mockImplementation(
|
||||||
|
async (
|
||||||
|
key: string,
|
||||||
|
value: string,
|
||||||
|
globalConfig?: boolean,
|
||||||
|
configPath?: string
|
||||||
|
) => {
|
||||||
|
const targetPath = configPath || localGitConfigPath
|
||||||
|
let content = await fs.promises.readFile(targetPath, 'utf8')
|
||||||
|
// Remove the includeIf section
|
||||||
|
const lines = content
|
||||||
|
.split('\n')
|
||||||
|
.filter(line => !line.includes('includeIf') && !line.includes(value))
|
||||||
|
await fs.promises.writeFile(targetPath, lines.join('\n'))
|
||||||
|
return true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
expect(credentialsFiles.length).toBe(1)
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await authHelper.removeAuth()
|
await authHelper.removeAuth()
|
||||||
|
|
||||||
// Assert includeIf entries removed from local git config
|
// Assert includeIf entries removed from local git config
|
||||||
localConfigContent = (
|
gitConfigContent = (
|
||||||
await fs.promises.readFile(localGitConfigPath)
|
await fs.promises.readFile(localGitConfigPath)
|
||||||
).toString()
|
).toString()
|
||||||
expect(localConfigContent.indexOf('includeIf.gitdir:')).toBeLessThan(0)
|
expect(gitConfigContent.indexOf('includeIf')).toBeLessThan(0)
|
||||||
|
expect(gitConfigContent.indexOf(credentialsFilePath)).toBeLessThan(0)
|
||||||
|
|
||||||
// Assert credentials config file deleted
|
// Assert credentials config file deleted
|
||||||
credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
try {
|
||||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
await fs.promises.stat(credentialsFilePath)
|
||||||
|
throw new Error('Credentials file should have been deleted')
|
||||||
|
} catch (err) {
|
||||||
|
if ((err as any)?.code !== 'ENOENT') {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const removeAuth_removesV6StyleCredentialsFromSubmodules =
|
||||||
|
'removeAuth removes v6 style credentials from submodules'
|
||||||
|
it(removeAuth_removesV6StyleCredentialsFromSubmodules, async () => {
|
||||||
|
// Arrange
|
||||||
|
await setup(removeAuth_removesV6StyleCredentialsFromSubmodules)
|
||||||
|
|
||||||
|
// Create fake submodule config paths
|
||||||
|
const submodule1Dir = path.join(workspace, '.git', 'modules', 'submodule-1')
|
||||||
|
const submodule1ConfigPath = path.join(submodule1Dir, 'config')
|
||||||
|
await fs.promises.mkdir(submodule1Dir, {recursive: true})
|
||||||
|
await fs.promises.writeFile(submodule1ConfigPath, '')
|
||||||
|
|
||||||
|
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||||
|
await authHelper.configureAuth()
|
||||||
|
|
||||||
|
// Create v6-style credentials file
|
||||||
|
const credentialsFileName =
|
||||||
|
'git-credentials-abcdef12-3456-7890-abcd-ef1234567890.config'
|
||||||
|
const credentialsFilePath = path.join(runnerTemp, credentialsFileName)
|
||||||
|
const basicCredential = Buffer.from(
|
||||||
|
`x-access-token:${settings.authToken}`,
|
||||||
|
'utf8'
|
||||||
|
).toString('base64')
|
||||||
|
const credentialsContent = `[http "https://github.com/"]\n\textraheader = AUTHORIZATION: basic ${basicCredential}\n`
|
||||||
|
await fs.promises.writeFile(credentialsFilePath, credentialsContent)
|
||||||
|
|
||||||
|
// Add includeIf entries to submodule config
|
||||||
|
const submodule1GitDir = submodule1Dir.replace(/\\/g, '/')
|
||||||
|
await fs.promises.appendFile(
|
||||||
|
submodule1ConfigPath,
|
||||||
|
`[includeIf "gitdir:${submodule1GitDir}/"]\n\tpath = ${credentialsFilePath}\n`
|
||||||
)
|
)
|
||||||
expect(credentialsFiles.length).toBe(0)
|
|
||||||
|
// Verify submodule config has includeIf entry
|
||||||
|
let submoduleConfigContent = (
|
||||||
|
await fs.promises.readFile(submodule1ConfigPath)
|
||||||
|
).toString()
|
||||||
|
expect(submoduleConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(
|
||||||
|
0
|
||||||
|
)
|
||||||
|
expect(
|
||||||
|
submoduleConfigContent.indexOf(credentialsFilePath)
|
||||||
|
).toBeGreaterThanOrEqual(0)
|
||||||
|
|
||||||
|
// Mock getSubmoduleConfigPaths
|
||||||
|
const mockGetSubmoduleConfigPaths =
|
||||||
|
git.getSubmoduleConfigPaths as jest.Mock<any, any>
|
||||||
|
mockGetSubmoduleConfigPaths.mockResolvedValue([submodule1ConfigPath])
|
||||||
|
|
||||||
|
// Mock tryGetConfigKeys for submodule
|
||||||
|
const mockTryGetConfigKeys = git.tryGetConfigKeys as jest.Mock<any, any>
|
||||||
|
mockTryGetConfigKeys.mockImplementation(
|
||||||
|
async (pattern: string, globalConfig?: boolean, configPath?: string) => {
|
||||||
|
if (configPath === submodule1ConfigPath) {
|
||||||
|
return [`includeIf.gitdir:${submodule1GitDir}/.path`]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock tryGetConfigValues for submodule
|
||||||
|
const mockTryGetConfigValues = git.tryGetConfigValues as jest.Mock<any, any>
|
||||||
|
mockTryGetConfigValues.mockImplementation(
|
||||||
|
async (key: string, globalConfig?: boolean, configPath?: string) => {
|
||||||
|
if (
|
||||||
|
configPath === submodule1ConfigPath &&
|
||||||
|
key === `includeIf.gitdir:${submodule1GitDir}/.path`
|
||||||
|
) {
|
||||||
|
return [credentialsFilePath]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock tryConfigUnsetValue for submodule
|
||||||
|
const mockTryConfigUnsetValue = git.tryConfigUnsetValue as jest.Mock<
|
||||||
|
any,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
mockTryConfigUnsetValue.mockImplementation(
|
||||||
|
async (
|
||||||
|
key: string,
|
||||||
|
value: string,
|
||||||
|
globalConfig?: boolean,
|
||||||
|
configPath?: string
|
||||||
|
) => {
|
||||||
|
const targetPath = configPath || localGitConfigPath
|
||||||
|
let content = await fs.promises.readFile(targetPath, 'utf8')
|
||||||
|
const lines = content
|
||||||
|
.split('\n')
|
||||||
|
.filter(line => !line.includes('includeIf') && !line.includes(value))
|
||||||
|
await fs.promises.writeFile(targetPath, lines.join('\n'))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await authHelper.removeAuth()
|
||||||
|
|
||||||
|
// Assert submodule includeIf entries removed
|
||||||
|
submoduleConfigContent = (
|
||||||
|
await fs.promises.readFile(submodule1ConfigPath)
|
||||||
|
).toString()
|
||||||
|
expect(submoduleConfigContent.indexOf('includeIf')).toBeLessThan(0)
|
||||||
|
expect(submoduleConfigContent.indexOf(credentialsFilePath)).toBeLessThan(0)
|
||||||
|
|
||||||
|
// Assert credentials file deleted
|
||||||
|
try {
|
||||||
|
await fs.promises.stat(credentialsFilePath)
|
||||||
|
throw new Error('Credentials file should have been deleted')
|
||||||
|
} catch (err) {
|
||||||
|
if ((err as any)?.code !== 'ENOENT') {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const removeAuth_skipsV6CleanupWhenEnvVarSet =
|
||||||
|
'removeAuth skips v6 cleanup when ACTIONS_CHECKOUT_SKIP_V6_CLEANUP is set'
|
||||||
|
it(removeAuth_skipsV6CleanupWhenEnvVarSet, async () => {
|
||||||
|
// Arrange
|
||||||
|
await setup(removeAuth_skipsV6CleanupWhenEnvVarSet)
|
||||||
|
|
||||||
|
// Set the skip environment variable
|
||||||
|
process.env['ACTIONS_CHECKOUT_SKIP_V6_CLEANUP'] = '1'
|
||||||
|
|
||||||
|
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||||
|
await authHelper.configureAuth()
|
||||||
|
|
||||||
|
// Create v6-style credentials file in RUNNER_TEMP
|
||||||
|
const credentialsFileName = 'git-credentials-test-uuid-1234-5678.config'
|
||||||
|
const credentialsFilePath = path.join(runnerTemp, credentialsFileName)
|
||||||
|
const credentialsContent =
|
||||||
|
'[http "https://github.com/"]\n\textraheader = AUTHORIZATION: basic token\n'
|
||||||
|
await fs.promises.writeFile(credentialsFilePath, credentialsContent)
|
||||||
|
|
||||||
|
// Add includeIf section to local git config (separate from http.* config)
|
||||||
|
const includeIfSection = `\n[includeIf "gitdir:/some/path/.git/"]\n\tpath = ${credentialsFilePath}\n`
|
||||||
|
await fs.promises.appendFile(localGitConfigPath, includeIfSection)
|
||||||
|
|
||||||
|
// Verify v6 style config exists
|
||||||
|
let gitConfigContent = (
|
||||||
|
await fs.promises.readFile(localGitConfigPath)
|
||||||
|
).toString()
|
||||||
|
expect(gitConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(0)
|
||||||
|
await fs.promises.stat(credentialsFilePath) // Verify file exists
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await authHelper.removeAuth()
|
||||||
|
|
||||||
|
// Assert v5 cleanup still happened (http.* removed)
|
||||||
|
gitConfigContent = (
|
||||||
|
await fs.promises.readFile(localGitConfigPath)
|
||||||
|
).toString()
|
||||||
|
expect(
|
||||||
|
gitConfigContent.indexOf('http.https://github.com/.extraheader')
|
||||||
|
).toBeLessThan(0)
|
||||||
|
|
||||||
|
// Assert v6 cleanup was skipped - includeIf should still be present
|
||||||
|
expect(gitConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(0)
|
||||||
|
expect(
|
||||||
|
gitConfigContent.indexOf(credentialsFilePath)
|
||||||
|
).toBeGreaterThanOrEqual(0)
|
||||||
|
|
||||||
|
// Assert credentials file still exists (wasn't deleted)
|
||||||
|
await fs.promises.stat(credentialsFilePath) // File should still exist
|
||||||
|
|
||||||
|
// Assert debug message was logged
|
||||||
|
expect(core.debug).toHaveBeenCalledWith(
|
||||||
|
'Skipping v6 style cleanup due to ACTIONS_CHECKOUT_SKIP_V6_CLEANUP'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
delete process.env['ACTIONS_CHECKOUT_SKIP_V6_CLEANUP']
|
||||||
})
|
})
|
||||||
|
|
||||||
const removeGlobalConfig_removesOverride =
|
const removeGlobalConfig_removesOverride =
|
||||||
@@ -762,28 +978,6 @@ describe('git-auth-helper tests', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const testCredentialsConfigPath_matchesCredentialsConfigPaths =
|
|
||||||
'testCredentialsConfigPath matches credentials config paths'
|
|
||||||
it(testCredentialsConfigPath_matchesCredentialsConfigPaths, async () => {
|
|
||||||
// Arrange
|
|
||||||
await setup(testCredentialsConfigPath_matchesCredentialsConfigPaths)
|
|
||||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
|
||||||
|
|
||||||
// Get a real credentials config path
|
|
||||||
const credentialsConfigPath = await (authHelper as any).getCredentialsConfigPath()
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
expect((authHelper as any).testCredentialsConfigPath(credentialsConfigPath)).toBe(true)
|
|
||||||
expect((authHelper as any).testCredentialsConfigPath('/some/path/git-credentials-12345678-abcd-1234-5678-123456789012.config')).toBe(true)
|
|
||||||
expect((authHelper as any).testCredentialsConfigPath('/some/path/git-credentials-abcdef12-3456-7890-abcd-ef1234567890.config')).toBe(true)
|
|
||||||
|
|
||||||
// Test invalid paths
|
|
||||||
expect((authHelper as any).testCredentialsConfigPath('/some/path/other-config.config')).toBe(false)
|
|
||||||
expect((authHelper as any).testCredentialsConfigPath('/some/path/git-credentials-invalid.config')).toBe(false)
|
|
||||||
expect((authHelper as any).testCredentialsConfigPath('/some/path/git-credentials-.config')).toBe(false)
|
|
||||||
expect((authHelper as any).testCredentialsConfigPath('')).toBe(false)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
async function setup(testName: string): Promise<void> {
|
async function setup(testName: string): Promise<void> {
|
||||||
@@ -798,7 +992,6 @@ async function setup(testName: string): Promise<void> {
|
|||||||
await fs.promises.mkdir(tempHomedir, {recursive: true})
|
await fs.promises.mkdir(tempHomedir, {recursive: true})
|
||||||
process.env['RUNNER_TEMP'] = runnerTemp
|
process.env['RUNNER_TEMP'] = runnerTemp
|
||||||
process.env['HOME'] = tempHomedir
|
process.env['HOME'] = tempHomedir
|
||||||
process.env['GITHUB_WORKSPACE'] = workspace
|
|
||||||
|
|
||||||
// Create git config
|
// Create git config
|
||||||
globalGitConfigPath = path.join(tempHomedir, '.gitconfig')
|
globalGitConfigPath = path.join(tempHomedir, '.gitconfig')
|
||||||
@@ -817,20 +1010,10 @@ async function setup(testName: string): Promise<void> {
|
|||||||
checkout: jest.fn(),
|
checkout: jest.fn(),
|
||||||
checkoutDetach: jest.fn(),
|
checkoutDetach: jest.fn(),
|
||||||
config: jest.fn(
|
config: jest.fn(
|
||||||
async (
|
async (key: string, value: string, globalConfig?: boolean) => {
|
||||||
key: string,
|
const configPath = globalConfig
|
||||||
value: string,
|
|
||||||
globalConfig?: boolean,
|
|
||||||
add?: boolean,
|
|
||||||
configFile?: string
|
|
||||||
) => {
|
|
||||||
const configPath =
|
|
||||||
configFile ||
|
|
||||||
(globalConfig
|
|
||||||
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
|
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
|
||||||
: localGitConfigPath)
|
: localGitConfigPath
|
||||||
// Ensure directory exists
|
|
||||||
await fs.promises.mkdir(path.dirname(configPath), {recursive: true})
|
|
||||||
await fs.promises.appendFile(configPath, `\n${key} ${value}`)
|
await fs.promises.appendFile(configPath, `\n${key} ${value}`)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@@ -850,7 +1033,6 @@ async function setup(testName: string): Promise<void> {
|
|||||||
env: {},
|
env: {},
|
||||||
fetch: jest.fn(),
|
fetch: jest.fn(),
|
||||||
getDefaultBranch: jest.fn(),
|
getDefaultBranch: jest.fn(),
|
||||||
getSubmoduleConfigPaths: jest.fn(async () => []),
|
|
||||||
getWorkingDirectory: jest.fn(() => workspace),
|
getWorkingDirectory: jest.fn(() => workspace),
|
||||||
init: jest.fn(),
|
init: jest.fn(),
|
||||||
isDetached: jest.fn(),
|
isDetached: jest.fn(),
|
||||||
@@ -889,53 +1071,20 @@ async function setup(testName: string): Promise<void> {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
tryConfigUnsetValue: jest.fn(
|
|
||||||
async (key: string, value: string, globalConfig?: boolean): Promise<boolean> => {
|
|
||||||
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(),
|
tryDisableAutomaticGarbageCollection: jest.fn(),
|
||||||
tryGetFetchUrl: jest.fn(),
|
tryGetFetchUrl: jest.fn(),
|
||||||
tryGetConfigValues: jest.fn(
|
getSubmoduleConfigPaths: jest.fn(async () => {
|
||||||
async (key: string, globalConfig?: boolean): Promise<string[]> => {
|
return []
|
||||||
const configPath = globalConfig
|
}),
|
||||||
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
|
tryConfigUnsetValue: jest.fn(async () => {
|
||||||
: localGitConfigPath
|
return true
|
||||||
const content = await fs.promises.readFile(configPath)
|
}),
|
||||||
const lines = content
|
tryGetConfigValues: jest.fn(async () => {
|
||||||
.toString()
|
return []
|
||||||
.split('\n')
|
}),
|
||||||
.filter(x => x && x.startsWith(key))
|
tryGetConfigKeys: jest.fn(async () => {
|
||||||
.map(x => x.substring(key.length).trim())
|
return []
|
||||||
return lines
|
}),
|
||||||
}
|
|
||||||
),
|
|
||||||
tryGetConfigKeys: jest.fn(
|
|
||||||
async (pattern: string, globalConfig?: boolean): Promise<string[]> => {
|
|
||||||
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(),
|
tryReset: jest.fn(),
|
||||||
version: jest.fn()
|
version: jest.fn()
|
||||||
}
|
}
|
||||||
@@ -970,7 +1119,6 @@ async function setup(testName: string): Promise<void> {
|
|||||||
|
|
||||||
async function getActualSshKeyPath(): Promise<string> {
|
async function getActualSshKeyPath(): Promise<string> {
|
||||||
let actualTempFiles = (await fs.promises.readdir(runnerTemp))
|
let actualTempFiles = (await fs.promises.readdir(runnerTemp))
|
||||||
.filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file
|
|
||||||
.sort()
|
.sort()
|
||||||
.map(x => path.join(runnerTemp, x))
|
.map(x => path.join(runnerTemp, x))
|
||||||
if (actualTempFiles.length === 0) {
|
if (actualTempFiles.length === 0) {
|
||||||
@@ -984,7 +1132,6 @@ async function getActualSshKeyPath(): Promise<string> {
|
|||||||
|
|
||||||
async function getActualSshKnownHostsPath(): Promise<string> {
|
async function getActualSshKnownHostsPath(): Promise<string> {
|
||||||
let actualTempFiles = (await fs.promises.readdir(runnerTemp))
|
let actualTempFiles = (await fs.promises.readdir(runnerTemp))
|
||||||
.filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file
|
|
||||||
.sort()
|
.sort()
|
||||||
.map(x => path.join(runnerTemp, x))
|
.map(x => path.join(runnerTemp, x))
|
||||||
if (actualTempFiles.length === 0) {
|
if (actualTempFiles.length === 0) {
|
||||||
|
|||||||
@@ -471,7 +471,6 @@ async function setup(testName: string): Promise<void> {
|
|||||||
configExists: jest.fn(),
|
configExists: jest.fn(),
|
||||||
fetch: jest.fn(),
|
fetch: jest.fn(),
|
||||||
getDefaultBranch: jest.fn(),
|
getDefaultBranch: jest.fn(),
|
||||||
getSubmoduleConfigPaths: jest.fn(async () => []),
|
|
||||||
getWorkingDirectory: jest.fn(() => repositoryPath),
|
getWorkingDirectory: jest.fn(() => repositoryPath),
|
||||||
init: jest.fn(),
|
init: jest.fn(),
|
||||||
isDetached: jest.fn(),
|
isDetached: jest.fn(),
|
||||||
@@ -494,15 +493,24 @@ async function setup(testName: string): Promise<void> {
|
|||||||
return true
|
return true
|
||||||
}),
|
}),
|
||||||
tryConfigUnset: jest.fn(),
|
tryConfigUnset: jest.fn(),
|
||||||
tryConfigUnsetValue: jest.fn(),
|
|
||||||
tryDisableAutomaticGarbageCollection: jest.fn(),
|
tryDisableAutomaticGarbageCollection: jest.fn(),
|
||||||
tryGetFetchUrl: jest.fn(async () => {
|
tryGetFetchUrl: jest.fn(async () => {
|
||||||
// Sanity check - this function shouldn't be called when the .git directory doesn't exist
|
// Sanity check - this function shouldn't be called when the .git directory doesn't exist
|
||||||
await fs.promises.stat(path.join(repositoryPath, '.git'))
|
await fs.promises.stat(path.join(repositoryPath, '.git'))
|
||||||
return repositoryUrl
|
return repositoryUrl
|
||||||
}),
|
}),
|
||||||
tryGetConfigValues: jest.fn(),
|
getSubmoduleConfigPaths: jest.fn(async () => {
|
||||||
tryGetConfigKeys: jest.fn(),
|
return []
|
||||||
|
}),
|
||||||
|
tryConfigUnsetValue: jest.fn(async () => {
|
||||||
|
return true
|
||||||
|
}),
|
||||||
|
tryGetConfigValues: jest.fn(async () => {
|
||||||
|
return []
|
||||||
|
}),
|
||||||
|
tryGetConfigKeys: jest.fn(async () => {
|
||||||
|
return []
|
||||||
|
}),
|
||||||
tryReset: jest.fn(async () => {
|
tryReset: jest.fn(async () => {
|
||||||
return true
|
return true
|
||||||
}),
|
}),
|
||||||
|
|||||||
232
dist/index.js
vendored
232
dist/index.js
vendored
@@ -162,7 +162,6 @@ class GitAuthHelper {
|
|||||||
this.sshKeyPath = '';
|
this.sshKeyPath = '';
|
||||||
this.sshKnownHostsPath = '';
|
this.sshKnownHostsPath = '';
|
||||||
this.temporaryHomePath = '';
|
this.temporaryHomePath = '';
|
||||||
this.credentialsConfigPath = ''; // Path to separate credentials config file in RUNNER_TEMP
|
|
||||||
this.git = gitCommandManager;
|
this.git = gitCommandManager;
|
||||||
this.settings = gitSourceSettings || {};
|
this.settings = gitSourceSettings || {};
|
||||||
// Token auth header
|
// Token auth header
|
||||||
@@ -230,10 +229,10 @@ class GitAuthHelper {
|
|||||||
configureGlobalAuth() {
|
configureGlobalAuth() {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
// 'configureTempGlobalConfig' noops if already set, just returns the path
|
// 'configureTempGlobalConfig' noops if already set, just returns the path
|
||||||
yield this.configureTempGlobalConfig();
|
const newGitConfigPath = yield this.configureTempGlobalConfig();
|
||||||
try {
|
try {
|
||||||
// Configure the token
|
// Configure the token
|
||||||
yield this.configureToken(true);
|
yield this.configureToken(newGitConfigPath, true);
|
||||||
// Configure HTTPS instead of SSH
|
// Configure HTTPS instead of SSH
|
||||||
yield this.git.tryConfigUnset(this.insteadOfKey, true);
|
yield this.git.tryConfigUnset(this.insteadOfKey, true);
|
||||||
if (!this.settings.sshKey) {
|
if (!this.settings.sshKey) {
|
||||||
@@ -253,34 +252,19 @@ class GitAuthHelper {
|
|||||||
configureSubmoduleAuth() {
|
configureSubmoduleAuth() {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
// Remove possible previous HTTPS instead of SSH
|
// Remove possible previous HTTPS instead of SSH
|
||||||
yield this.removeSubmoduleGitConfig(this.insteadOfKey);
|
yield this.removeGitConfig(this.insteadOfKey, true);
|
||||||
if (this.settings.persistCredentials) {
|
if (this.settings.persistCredentials) {
|
||||||
// Credentials config path
|
// Configure a placeholder value. This approach avoids the credential being captured
|
||||||
const credentialsConfigPath = yield this.getCredentialsConfigPath();
|
// by process creation audit events, which are commonly logged. For more information,
|
||||||
// Container credentials config path
|
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
|
||||||
const containerCredentialsPath = path.posix.join('/github/runner_temp', path.basename(credentialsConfigPath));
|
const output = yield this.git.submoduleForeach(
|
||||||
// Container repo path
|
// wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
|
||||||
const workingDirectory = this.git.getWorkingDirectory();
|
`sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules);
|
||||||
const githubWorkspace = process.env['GITHUB_WORKSPACE'];
|
// Replace the placeholder
|
||||||
assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined');
|
const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || [];
|
||||||
let relativePath = path.relative(githubWorkspace, workingDirectory);
|
|
||||||
relativePath = relativePath.replace(/\\/g, '/');
|
|
||||||
const containerRepoPath = path.posix.join('/github/workspace', relativePath);
|
|
||||||
// Get submodule config file paths.
|
|
||||||
const configPaths = yield this.git.getSubmoduleConfigPaths(this.settings.nestedSubmodules);
|
|
||||||
// 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) {
|
for (const configPath of configPaths) {
|
||||||
// Submodule Git directory
|
core.debug(`Replacing token placeholder in '${configPath}'`);
|
||||||
let submoduleGitDir = path.dirname(configPath); // The config file is at .git/modules/submodule-name/config
|
yield this.replaceTokenPlaceholder(configPath);
|
||||||
submoduleGitDir = submoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
|
|
||||||
// Configure host includeIf
|
|
||||||
yield this.git.config(`includeIf.gitdir:${submoduleGitDir}.path`, credentialsConfigPath, false, false, configPath);
|
|
||||||
// Configure container includeIf
|
|
||||||
let relativeSubmoduleGitDir = path.relative(githubWorkspace, submoduleGitDir);
|
|
||||||
relativeSubmoduleGitDir = relativeSubmoduleGitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
|
|
||||||
const containerSubmoduleGitDir = path.posix.join('/github/workspace', relativeSubmoduleGitDir);
|
|
||||||
yield this.git.config(`includeIf.gitdir:${containerSubmoduleGitDir}.path`, containerCredentialsPath, false, false, configPath);
|
|
||||||
}
|
}
|
||||||
if (this.settings.sshKey) {
|
if (this.settings.sshKey) {
|
||||||
// Configure core.sshCommand
|
// Configure core.sshCommand
|
||||||
@@ -311,10 +295,6 @@ class GitAuthHelper {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Configures SSH authentication by writing the SSH key and known hosts,
|
|
||||||
* and setting up the GIT_SSH_COMMAND environment variable.
|
|
||||||
*/
|
|
||||||
configureSsh() {
|
configureSsh() {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
if (!this.settings.sshKey) {
|
if (!this.settings.sshKey) {
|
||||||
@@ -371,87 +351,43 @@ class GitAuthHelper {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
configureToken(configPath, globalConfig) {
|
||||||
* Configures token-based authentication by creating a credentials config file
|
|
||||||
* and setting up includeIf entries to reference it.
|
|
||||||
* @param globalConfig Whether to configure global config instead of local
|
|
||||||
*/
|
|
||||||
configureToken(globalConfig) {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
// Get the credentials config file path in RUNNER_TEMP
|
// Validate args
|
||||||
const credentialsConfigPath = yield this.getCredentialsConfigPath();
|
assert.ok((configPath && globalConfig) || (!configPath && !globalConfig), 'Unexpected configureToken parameter combinations');
|
||||||
// Write placeholder to the separate credentials config file using git config.
|
// Default config path
|
||||||
// This approach avoids the credential being captured by process creation audit events,
|
if (!configPath && !globalConfig) {
|
||||||
// which are commonly logged. For more information, refer to
|
configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config');
|
||||||
// https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
|
}
|
||||||
yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, false, false, credentialsConfigPath);
|
// Configure a placeholder value. This approach avoids the credential being captured
|
||||||
// Replace the placeholder in the credentials config file
|
// by process creation audit events, which are commonly logged. For more information,
|
||||||
let content = (yield fs.promises.readFile(credentialsConfigPath)).toString();
|
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
|
||||||
|
yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, globalConfig);
|
||||||
|
// Replace the placeholder
|
||||||
|
yield this.replaceTokenPlaceholder(configPath || '');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
replaceTokenPlaceholder(configPath) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
assert.ok(configPath, 'configPath is not defined');
|
||||||
|
let content = (yield fs.promises.readFile(configPath)).toString();
|
||||||
const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue);
|
const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue);
|
||||||
if (placeholderIndex < 0 ||
|
if (placeholderIndex < 0 ||
|
||||||
placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)) {
|
placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)) {
|
||||||
throw new Error(`Unable to replace auth placeholder in ${credentialsConfigPath}`);
|
throw new Error(`Unable to replace auth placeholder in ${configPath}`);
|
||||||
}
|
}
|
||||||
assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined');
|
assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined');
|
||||||
content = content.replace(this.tokenPlaceholderConfigValue, this.tokenConfigValue);
|
content = content.replace(this.tokenPlaceholderConfigValue, this.tokenConfigValue);
|
||||||
yield fs.promises.writeFile(credentialsConfigPath, content);
|
yield fs.promises.writeFile(configPath, content);
|
||||||
// Add include or includeIf to reference the credentials config
|
|
||||||
if (globalConfig) {
|
|
||||||
// Global config file is temporary
|
|
||||||
yield this.git.config('include.path', credentialsConfigPath, true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Host git directory
|
|
||||||
let gitDir = path.join(this.git.getWorkingDirectory(), '.git');
|
|
||||||
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);
|
|
||||||
// Container git directory
|
|
||||||
const githubWorkspace = process.env['GITHUB_WORKSPACE'];
|
|
||||||
assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined');
|
|
||||||
const workingDirectory = this.git.getWorkingDirectory();
|
|
||||||
let relativePath = path.relative(githubWorkspace, workingDirectory);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Gets or creates the path to the credentials config file in RUNNER_TEMP.
|
|
||||||
* @returns The absolute path to the credentials config file
|
|
||||||
*/
|
|
||||||
getCredentialsConfigPath() {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
if (this.credentialsConfigPath) {
|
|
||||||
return this.credentialsConfigPath;
|
|
||||||
}
|
|
||||||
const runnerTemp = process.env['RUNNER_TEMP'] || '';
|
|
||||||
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined');
|
|
||||||
// Create a unique filename for this checkout instance
|
|
||||||
const configFileName = `git-credentials-${(0, uuid_1.v4)()}.config`;
|
|
||||||
this.credentialsConfigPath = path.join(runnerTemp, configFileName);
|
|
||||||
core.debug(`Credentials config path: ${this.credentialsConfigPath}`);
|
|
||||||
return this.credentialsConfigPath;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Removes SSH authentication configuration by cleaning up SSH keys,
|
|
||||||
* known hosts files, and SSH command configurations.
|
|
||||||
*/
|
|
||||||
removeSsh() {
|
removeSsh() {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
var _a, _b;
|
var _a;
|
||||||
// SSH key
|
// SSH key
|
||||||
const keyPath = this.sshKeyPath || stateHelper.SshKeyPath;
|
const keyPath = this.sshKeyPath || stateHelper.SshKeyPath;
|
||||||
if (keyPath) {
|
if (keyPath) {
|
||||||
try {
|
try {
|
||||||
core.info(`Removing SSH key '${keyPath}'`);
|
|
||||||
yield io.rmRF(keyPath);
|
yield io.rmRF(keyPath);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -463,91 +399,82 @@ class GitAuthHelper {
|
|||||||
const knownHostsPath = this.sshKnownHostsPath || stateHelper.SshKnownHostsPath;
|
const knownHostsPath = this.sshKnownHostsPath || stateHelper.SshKnownHostsPath;
|
||||||
if (knownHostsPath) {
|
if (knownHostsPath) {
|
||||||
try {
|
try {
|
||||||
core.info(`Removing SSH known hosts '${knownHostsPath}'`);
|
|
||||||
yield io.rmRF(knownHostsPath);
|
yield io.rmRF(knownHostsPath);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (_b) {
|
||||||
core.debug(`${(_b = err === null || err === void 0 ? void 0 : err.message) !== null && _b !== void 0 ? _b : err}`);
|
// Intentionally empty
|
||||||
core.warning(`Failed to remove SSH known hosts '${knownHostsPath}'`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// SSH command
|
// SSH command
|
||||||
core.info("Removing SSH command configuration");
|
|
||||||
yield this.removeGitConfig(SSH_COMMAND_KEY);
|
yield this.removeGitConfig(SSH_COMMAND_KEY);
|
||||||
yield this.removeSubmoduleGitConfig(SSH_COMMAND_KEY);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Removes token-based authentication by cleaning up HTTP headers,
|
|
||||||
* includeIf entries, and credentials config files.
|
|
||||||
*/
|
|
||||||
removeToken() {
|
removeToken() {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
var _a;
|
// Remove HTTP extra header from local git config and submodule configs
|
||||||
// Remove HTTP extra header
|
|
||||||
core.info("Removing HTTP extra header");
|
|
||||||
yield this.removeGitConfig(this.tokenConfigKey);
|
yield this.removeGitConfig(this.tokenConfigKey);
|
||||||
yield this.removeSubmoduleGitConfig(this.tokenConfigKey);
|
//
|
||||||
|
// Cleanup actions/checkout@v6 style credentials
|
||||||
|
//
|
||||||
|
const skipV6Cleanup = process.env['ACTIONS_CHECKOUT_SKIP_V6_CLEANUP'];
|
||||||
|
if (skipV6Cleanup === '1' || (skipV6Cleanup === null || skipV6Cleanup === void 0 ? void 0 : skipV6Cleanup.toLowerCase()) === 'true') {
|
||||||
|
core.debug('Skipping v6 style cleanup due to ACTIONS_CHECKOUT_SKIP_V6_CLEANUP');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
// Collect credentials config paths that need to be removed
|
// Collect credentials config paths that need to be removed
|
||||||
const credentialsPaths = new Set();
|
const credentialsPaths = new Set();
|
||||||
// Remove includeIf entries that point to git-credentials-*.config files
|
// Remove includeIf entries that point to git-credentials-*.config files
|
||||||
core.info("Removing includeIf entries pointing to credentials config files");
|
|
||||||
const mainCredentialsPaths = yield this.removeIncludeIfCredentials();
|
const mainCredentialsPaths = yield this.removeIncludeIfCredentials();
|
||||||
mainCredentialsPaths.forEach(path => credentialsPaths.add(path));
|
mainCredentialsPaths.forEach(path => credentialsPaths.add(path));
|
||||||
// Remove submodule includeIf entries that point to git-credentials-*.config files
|
// Remove submodule includeIf entries that point to git-credentials-*.config files
|
||||||
|
try {
|
||||||
const submoduleConfigPaths = yield this.git.getSubmoduleConfigPaths(true);
|
const submoduleConfigPaths = yield this.git.getSubmoduleConfigPaths(true);
|
||||||
for (const configPath of submoduleConfigPaths) {
|
for (const configPath of submoduleConfigPaths) {
|
||||||
const submoduleCredentialsPaths = yield this.removeIncludeIfCredentials(configPath);
|
const submoduleCredentialsPaths = yield this.removeIncludeIfCredentials(configPath);
|
||||||
submoduleCredentialsPaths.forEach(path => credentialsPaths.add(path));
|
submoduleCredentialsPaths.forEach(path => credentialsPaths.add(path));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
core.debug(`Unable to get submodule config paths: ${err}`);
|
||||||
|
}
|
||||||
// Remove credentials config files
|
// Remove credentials config files
|
||||||
for (const credentialsPath of credentialsPaths) {
|
for (const credentialsPath of credentialsPaths) {
|
||||||
// Only remove credentials config files if they are under RUNNER_TEMP
|
// Only remove credentials config files if they are under RUNNER_TEMP
|
||||||
const runnerTemp = process.env['RUNNER_TEMP'];
|
const runnerTemp = process.env['RUNNER_TEMP'];
|
||||||
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined');
|
if (runnerTemp && credentialsPath.startsWith(runnerTemp)) {
|
||||||
if (credentialsPath.startsWith(runnerTemp)) {
|
|
||||||
try {
|
try {
|
||||||
core.info(`Removing credentials config '${credentialsPath}'`);
|
|
||||||
yield io.rmRF(credentialsPath);
|
yield io.rmRF(credentialsPath);
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
core.debug(`${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`);
|
core.debug(`Failed to remove credentials config '${credentialsPath}': ${err}`);
|
||||||
core.warning(`Failed to remove credentials config '${credentialsPath}'`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
core.debug(`Skipping removal of credentials config '${credentialsPath}' - not under RUNNER_TEMP`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (err) {
|
||||||
|
core.debug(`Failed to cleanup v6 style credentials: ${err}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
removeGitConfig(configKey_1) {
|
||||||
* Removes a git config key from the local repository config.
|
return __awaiter(this, arguments, void 0, function* (configKey, submoduleOnly = false) {
|
||||||
* @param configKey The git config key to remove
|
if (!submoduleOnly) {
|
||||||
*/
|
|
||||||
removeGitConfig(configKey) {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
if ((yield this.git.configExists(configKey)) &&
|
if ((yield this.git.configExists(configKey)) &&
|
||||||
!(yield this.git.tryConfigUnset(configKey))) {
|
!(yield this.git.tryConfigUnset(configKey))) {
|
||||||
// Load the config contents
|
// Load the config contents
|
||||||
core.warning(`Failed to remove '${configKey}' from the git config`);
|
core.warning(`Failed to remove '${configKey}' from the git config`);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Removes a git config key from all submodule configs.
|
|
||||||
* @param configKey The git config key to remove
|
|
||||||
*/
|
|
||||||
removeSubmoduleGitConfig(configKey) {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
const pattern = regexpHelper.escape(configKey);
|
const pattern = regexpHelper.escape(configKey);
|
||||||
yield this.git.submoduleForeach(
|
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);
|
`sh -c "git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :"`, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Removes includeIf entries that point to git-credentials-*.config files.
|
* Removes includeIf entries that point to git-credentials-*.config files.
|
||||||
|
* This handles cleanup of credentials configured by newer versions of the action.
|
||||||
* @param configPath Optional path to a specific git config file to operate on
|
* @param configPath Optional path to a specific git config file to operate on
|
||||||
* @returns Array of unique credentials config file paths that were found and removed
|
* @returns Array of unique credentials config file paths that were found and removed
|
||||||
*/
|
*/
|
||||||
@@ -556,10 +483,12 @@ class GitAuthHelper {
|
|||||||
const credentialsPaths = new Set();
|
const credentialsPaths = new Set();
|
||||||
try {
|
try {
|
||||||
// Get all includeIf.gitdir keys
|
// Get all includeIf.gitdir keys
|
||||||
const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir:', false, configPath);
|
const keys = yield this.git.tryGetConfigKeys('^includeIf\\.gitdir:', false, // globalConfig?
|
||||||
|
configPath);
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
// Get all values for this key
|
// Get all values for this key
|
||||||
const values = yield this.git.tryGetConfigValues(key, false, configPath);
|
const values = yield this.git.tryGetConfigValues(key, false, // globalConfig?
|
||||||
|
configPath);
|
||||||
if (values.length > 0) {
|
if (values.length > 0) {
|
||||||
// Remove only values that match git-credentials-<uuid>.config pattern
|
// Remove only values that match git-credentials-<uuid>.config pattern
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
@@ -573,18 +502,13 @@ class GitAuthHelper {
|
|||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
// Ignore errors - this is cleanup code
|
// Ignore errors - this is cleanup code
|
||||||
if (configPath) {
|
core.debug(`Error during includeIf cleanup${configPath ? ` for ${configPath}` : ''}: ${err}`);
|
||||||
core.debug(`Error during includeIf cleanup for ${configPath}: ${err}`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
core.debug(`Error during includeIf cleanup: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Array.from(credentialsPaths);
|
return Array.from(credentialsPaths);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Tests if a path matches the git-credentials-*.config pattern.
|
* Tests if a path matches the git-credentials-*.config pattern used by newer versions.
|
||||||
* @param path The path to test
|
* @param path The path to test
|
||||||
* @returns True if the path matches the credentials config pattern
|
* @returns True if the path matches the credentials config pattern
|
||||||
*/
|
*/
|
||||||
@@ -788,15 +712,9 @@ class GitCommandManager {
|
|||||||
yield this.execGit(args);
|
yield this.execGit(args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
config(configKey, configValue, globalConfig, add, configFile) {
|
config(configKey, configValue, globalConfig, add) {
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
const args = ['config'];
|
const args = ['config', globalConfig ? '--global' : '--local'];
|
||||||
if (configFile) {
|
|
||||||
args.push('--file', configFile);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
args.push(globalConfig ? '--global' : '--local');
|
|
||||||
}
|
|
||||||
if (add) {
|
if (add) {
|
||||||
args.push('--add');
|
args.push('--add');
|
||||||
}
|
}
|
||||||
@@ -1060,7 +978,10 @@ class GitCommandManager {
|
|||||||
if (output.exitCode !== 0) {
|
if (output.exitCode !== 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return output.stdout.trim().split('\n').filter(value => value.trim());
|
return output.stdout
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter(value => value.trim());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
tryGetConfigKeys(pattern, globalConfig, configFile) {
|
tryGetConfigKeys(pattern, globalConfig, configFile) {
|
||||||
@@ -1077,7 +998,10 @@ class GitCommandManager {
|
|||||||
if (output.exitCode !== 0) {
|
if (output.exitCode !== 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return output.stdout.trim().split('\n').filter(key => key.trim());
|
return output.stdout
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter(key => key.trim());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
tryReset() {
|
tryReset() {
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ class GitAuthHelper {
|
|||||||
private sshKeyPath = ''
|
private sshKeyPath = ''
|
||||||
private sshKnownHostsPath = ''
|
private sshKnownHostsPath = ''
|
||||||
private temporaryHomePath = ''
|
private temporaryHomePath = ''
|
||||||
private credentialsConfigPath = '' // Path to separate credentials config file in RUNNER_TEMP
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
gitCommandManager: IGitCommandManager,
|
gitCommandManager: IGitCommandManager,
|
||||||
@@ -127,10 +126,10 @@ class GitAuthHelper {
|
|||||||
|
|
||||||
async configureGlobalAuth(): Promise<void> {
|
async configureGlobalAuth(): Promise<void> {
|
||||||
// 'configureTempGlobalConfig' noops if already set, just returns the path
|
// 'configureTempGlobalConfig' noops if already set, just returns the path
|
||||||
await this.configureTempGlobalConfig()
|
const newGitConfigPath = await this.configureTempGlobalConfig()
|
||||||
try {
|
try {
|
||||||
// Configure the token
|
// Configure the token
|
||||||
await this.configureToken(true)
|
await this.configureToken(newGitConfigPath, true)
|
||||||
|
|
||||||
// Configure HTTPS instead of SSH
|
// Configure HTTPS instead of SSH
|
||||||
await this.git.tryConfigUnset(this.insteadOfKey, true)
|
await this.git.tryConfigUnset(this.insteadOfKey, true)
|
||||||
@@ -151,67 +150,24 @@ class GitAuthHelper {
|
|||||||
|
|
||||||
async configureSubmoduleAuth(): Promise<void> {
|
async configureSubmoduleAuth(): Promise<void> {
|
||||||
// Remove possible previous HTTPS instead of SSH
|
// Remove possible previous HTTPS instead of SSH
|
||||||
await this.removeSubmoduleGitConfig(this.insteadOfKey)
|
await this.removeGitConfig(this.insteadOfKey, true)
|
||||||
|
|
||||||
if (this.settings.persistCredentials) {
|
if (this.settings.persistCredentials) {
|
||||||
// Credentials config path
|
// Configure a placeholder value. This approach avoids the credential being captured
|
||||||
const credentialsConfigPath = await this.getCredentialsConfigPath()
|
// by process creation audit events, which are commonly logged. For more information,
|
||||||
|
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
|
||||||
// Container credentials config path
|
const output = await this.git.submoduleForeach(
|
||||||
const containerCredentialsPath = path.posix.join(
|
// wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
|
||||||
'/github/runner_temp',
|
`sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`,
|
||||||
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')
|
|
||||||
let relativePath = path.relative(githubWorkspace, workingDirectory)
|
|
||||||
relativePath = relativePath.replace(/\\/g, '/')
|
|
||||||
const containerRepoPath = path.posix.join(
|
|
||||||
'/github/workspace',
|
|
||||||
relativePath
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get submodule config file paths.
|
|
||||||
const configPaths = await this.git.getSubmoduleConfigPaths(
|
|
||||||
this.settings.nestedSubmodules
|
this.settings.nestedSubmodules
|
||||||
)
|
)
|
||||||
|
|
||||||
// For each submodule, configure includeIf entries pointing to the shared credentials file.
|
// Replace the placeholder
|
||||||
// Configure both host and container paths to support Docker container actions.
|
const configPaths: string[] =
|
||||||
|
output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []
|
||||||
for (const configPath of configPaths) {
|
for (const configPath of configPaths) {
|
||||||
// Submodule Git directory
|
core.debug(`Replacing token placeholder in '${configPath}'`)
|
||||||
let submoduleGitDir = path.dirname(configPath) // The config file is at .git/modules/submodule-name/config
|
await this.replaceTokenPlaceholder(configPath)
|
||||||
submoduleGitDir = submoduleGitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows
|
|
||||||
|
|
||||||
// Configure host includeIf
|
|
||||||
await this.git.config(
|
|
||||||
`includeIf.gitdir:${submoduleGitDir}.path`,
|
|
||||||
credentialsConfigPath,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
configPath
|
|
||||||
)
|
|
||||||
|
|
||||||
// Configure container includeIf
|
|
||||||
let relativeSubmoduleGitDir = path.relative(
|
|
||||||
githubWorkspace,
|
|
||||||
submoduleGitDir
|
|
||||||
)
|
|
||||||
relativeSubmoduleGitDir = relativeSubmoduleGitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows
|
|
||||||
const containerSubmoduleGitDir = path.posix.join(
|
|
||||||
'/github/workspace',
|
|
||||||
relativeSubmoduleGitDir
|
|
||||||
)
|
|
||||||
await this.git.config(
|
|
||||||
`includeIf.gitdir:${containerSubmoduleGitDir}.path`,
|
|
||||||
containerCredentialsPath,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
configPath
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.settings.sshKey) {
|
if (this.settings.sshKey) {
|
||||||
@@ -245,10 +201,6 @@ class GitAuthHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures SSH authentication by writing the SSH key and known hosts,
|
|
||||||
* and setting up the GIT_SSH_COMMAND environment variable.
|
|
||||||
*/
|
|
||||||
private async configureSsh(): Promise<void> {
|
private async configureSsh(): Promise<void> {
|
||||||
if (!this.settings.sshKey) {
|
if (!this.settings.sshKey) {
|
||||||
return
|
return
|
||||||
@@ -320,110 +272,57 @@ class GitAuthHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private async configureToken(
|
||||||
* Configures token-based authentication by creating a credentials config file
|
configPath?: string,
|
||||||
* and setting up includeIf entries to reference it.
|
globalConfig?: boolean
|
||||||
* @param globalConfig Whether to configure global config instead of local
|
): Promise<void> {
|
||||||
*/
|
// Validate args
|
||||||
private async configureToken(globalConfig?: boolean): Promise<void> {
|
assert.ok(
|
||||||
// Get the credentials config file path in RUNNER_TEMP
|
(configPath && globalConfig) || (!configPath && !globalConfig),
|
||||||
const credentialsConfigPath = await this.getCredentialsConfigPath()
|
'Unexpected configureToken parameter combinations'
|
||||||
|
)
|
||||||
|
|
||||||
// Write placeholder to the separate credentials config file using git config.
|
// Default config path
|
||||||
// This approach avoids the credential being captured by process creation audit events,
|
if (!configPath && !globalConfig) {
|
||||||
// which are commonly logged. For more information, refer to
|
configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config')
|
||||||
// https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
|
}
|
||||||
|
|
||||||
|
// Configure a placeholder value. This approach avoids the credential being captured
|
||||||
|
// by process creation audit events, which are commonly logged. For more information,
|
||||||
|
// refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
|
||||||
await this.git.config(
|
await this.git.config(
|
||||||
this.tokenConfigKey,
|
this.tokenConfigKey,
|
||||||
this.tokenPlaceholderConfigValue,
|
this.tokenPlaceholderConfigValue,
|
||||||
false,
|
globalConfig
|
||||||
false,
|
|
||||||
credentialsConfigPath
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Replace the placeholder in the credentials config file
|
// Replace the placeholder
|
||||||
let content = (await fs.promises.readFile(credentialsConfigPath)).toString()
|
await this.replaceTokenPlaceholder(configPath || '')
|
||||||
|
}
|
||||||
|
|
||||||
|
private async replaceTokenPlaceholder(configPath: string): Promise<void> {
|
||||||
|
assert.ok(configPath, 'configPath is not defined')
|
||||||
|
let content = (await fs.promises.readFile(configPath)).toString()
|
||||||
const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue)
|
const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue)
|
||||||
if (
|
if (
|
||||||
placeholderIndex < 0 ||
|
placeholderIndex < 0 ||
|
||||||
placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)
|
placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)
|
||||||
) {
|
) {
|
||||||
throw new Error(`Unable to replace auth placeholder in ${credentialsConfigPath}`)
|
throw new Error(`Unable to replace auth placeholder in ${configPath}`)
|
||||||
}
|
}
|
||||||
assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined')
|
assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined')
|
||||||
content = content.replace(
|
content = content.replace(
|
||||||
this.tokenPlaceholderConfigValue,
|
this.tokenPlaceholderConfigValue,
|
||||||
this.tokenConfigValue
|
this.tokenConfigValue
|
||||||
)
|
)
|
||||||
await fs.promises.writeFile(credentialsConfigPath, content)
|
await fs.promises.writeFile(configPath, content)
|
||||||
|
|
||||||
// Add include or includeIf to reference the credentials config
|
|
||||||
if (globalConfig) {
|
|
||||||
// Global config file is temporary
|
|
||||||
await this.git.config('include.path', credentialsConfigPath, true)
|
|
||||||
} else {
|
|
||||||
// Host git directory
|
|
||||||
let gitDir = path.join(this.git.getWorkingDirectory(), '.git')
|
|
||||||
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)
|
|
||||||
|
|
||||||
// Container git directory
|
|
||||||
const githubWorkspace = process.env['GITHUB_WORKSPACE']
|
|
||||||
assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined')
|
|
||||||
const workingDirectory = this.git.getWorkingDirectory()
|
|
||||||
let relativePath = path.relative(githubWorkspace, workingDirectory)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets or creates the path to the credentials config file in RUNNER_TEMP.
|
|
||||||
* @returns The absolute path to the credentials config file
|
|
||||||
*/
|
|
||||||
private async getCredentialsConfigPath(): Promise<string> {
|
|
||||||
if (this.credentialsConfigPath) {
|
|
||||||
return this.credentialsConfigPath
|
|
||||||
}
|
|
||||||
|
|
||||||
const runnerTemp = process.env['RUNNER_TEMP'] || ''
|
|
||||||
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
|
|
||||||
|
|
||||||
// Create a unique filename for this checkout instance
|
|
||||||
const configFileName = `git-credentials-${uuid()}.config`
|
|
||||||
this.credentialsConfigPath = path.join(runnerTemp, configFileName)
|
|
||||||
|
|
||||||
core.debug(`Credentials config path: ${this.credentialsConfigPath}`)
|
|
||||||
return this.credentialsConfigPath
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes SSH authentication configuration by cleaning up SSH keys,
|
|
||||||
* known hosts files, and SSH command configurations.
|
|
||||||
*/
|
|
||||||
private async removeSsh(): Promise<void> {
|
private async removeSsh(): Promise<void> {
|
||||||
// SSH key
|
// SSH key
|
||||||
const keyPath = this.sshKeyPath || stateHelper.SshKeyPath
|
const keyPath = this.sshKeyPath || stateHelper.SshKeyPath
|
||||||
if (keyPath) {
|
if (keyPath) {
|
||||||
try {
|
try {
|
||||||
core.info(`Removing SSH key '${keyPath}'`)
|
|
||||||
await io.rmRF(keyPath)
|
await io.rmRF(keyPath)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
core.debug(`${(err as any)?.message ?? err}`)
|
core.debug(`${(err as any)?.message ?? err}`)
|
||||||
@@ -436,71 +335,76 @@ class GitAuthHelper {
|
|||||||
this.sshKnownHostsPath || stateHelper.SshKnownHostsPath
|
this.sshKnownHostsPath || stateHelper.SshKnownHostsPath
|
||||||
if (knownHostsPath) {
|
if (knownHostsPath) {
|
||||||
try {
|
try {
|
||||||
core.info(`Removing SSH known hosts '${knownHostsPath}'`)
|
|
||||||
await io.rmRF(knownHostsPath)
|
await io.rmRF(knownHostsPath)
|
||||||
} catch (err) {
|
} catch {
|
||||||
core.debug(`${(err as any)?.message ?? err}`)
|
// Intentionally empty
|
||||||
core.warning(`Failed to remove SSH known hosts '${knownHostsPath}'`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSH command
|
// SSH command
|
||||||
core.info("Removing SSH command configuration")
|
|
||||||
await this.removeGitConfig(SSH_COMMAND_KEY)
|
await this.removeGitConfig(SSH_COMMAND_KEY)
|
||||||
await this.removeSubmoduleGitConfig(SSH_COMMAND_KEY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes token-based authentication by cleaning up HTTP headers,
|
|
||||||
* includeIf entries, and credentials config files.
|
|
||||||
*/
|
|
||||||
private async removeToken(): Promise<void> {
|
private async removeToken(): Promise<void> {
|
||||||
// Remove HTTP extra header
|
// Remove HTTP extra header from local git config and submodule configs
|
||||||
core.info("Removing HTTP extra header")
|
|
||||||
await this.removeGitConfig(this.tokenConfigKey)
|
await this.removeGitConfig(this.tokenConfigKey)
|
||||||
await this.removeSubmoduleGitConfig(this.tokenConfigKey)
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Cleanup actions/checkout@v6 style credentials
|
||||||
|
//
|
||||||
|
const skipV6Cleanup = process.env['ACTIONS_CHECKOUT_SKIP_V6_CLEANUP']
|
||||||
|
if (skipV6Cleanup === '1' || skipV6Cleanup?.toLowerCase() === 'true') {
|
||||||
|
core.debug(
|
||||||
|
'Skipping v6 style cleanup due to ACTIONS_CHECKOUT_SKIP_V6_CLEANUP'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// Collect credentials config paths that need to be removed
|
// Collect credentials config paths that need to be removed
|
||||||
const credentialsPaths = new Set<string>()
|
const credentialsPaths = new Set<string>()
|
||||||
|
|
||||||
// Remove includeIf entries that point to git-credentials-*.config files
|
// Remove includeIf entries that point to git-credentials-*.config files
|
||||||
core.info("Removing includeIf entries pointing to credentials config files")
|
|
||||||
const mainCredentialsPaths = await this.removeIncludeIfCredentials()
|
const mainCredentialsPaths = await this.removeIncludeIfCredentials()
|
||||||
mainCredentialsPaths.forEach(path => credentialsPaths.add(path))
|
mainCredentialsPaths.forEach(path => credentialsPaths.add(path))
|
||||||
|
|
||||||
// Remove submodule includeIf entries that point to git-credentials-*.config files
|
// Remove submodule includeIf entries that point to git-credentials-*.config files
|
||||||
const submoduleConfigPaths = await this.git.getSubmoduleConfigPaths(true)
|
try {
|
||||||
|
const submoduleConfigPaths =
|
||||||
|
await this.git.getSubmoduleConfigPaths(true)
|
||||||
for (const configPath of submoduleConfigPaths) {
|
for (const configPath of submoduleConfigPaths) {
|
||||||
const submoduleCredentialsPaths = await this.removeIncludeIfCredentials(configPath)
|
const submoduleCredentialsPaths =
|
||||||
|
await this.removeIncludeIfCredentials(configPath)
|
||||||
submoduleCredentialsPaths.forEach(path => credentialsPaths.add(path))
|
submoduleCredentialsPaths.forEach(path => credentialsPaths.add(path))
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
core.debug(`Unable to get submodule config paths: ${err}`)
|
||||||
|
}
|
||||||
|
|
||||||
// Remove credentials config files
|
// Remove credentials config files
|
||||||
for (const credentialsPath of credentialsPaths) {
|
for (const credentialsPath of credentialsPaths) {
|
||||||
// Only remove credentials config files if they are under RUNNER_TEMP
|
// Only remove credentials config files if they are under RUNNER_TEMP
|
||||||
const runnerTemp = process.env['RUNNER_TEMP']
|
const runnerTemp = process.env['RUNNER_TEMP']
|
||||||
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
|
if (runnerTemp && credentialsPath.startsWith(runnerTemp)) {
|
||||||
if (credentialsPath.startsWith(runnerTemp)) {
|
|
||||||
try {
|
try {
|
||||||
core.info(`Removing credentials config '${credentialsPath}'`)
|
|
||||||
await io.rmRF(credentialsPath)
|
await io.rmRF(credentialsPath)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
core.debug(`${(err as any)?.message ?? err}`)
|
core.debug(
|
||||||
core.warning(
|
`Failed to remove credentials config '${credentialsPath}': ${err}`
|
||||||
`Failed to remove credentials config '${credentialsPath}'`
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
core.debug(`Skipping removal of credentials config '${credentialsPath}' - not under RUNNER_TEMP`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
core.debug(`Failed to cleanup v6 style credentials: ${err}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private async removeGitConfig(
|
||||||
* Removes a git config key from the local repository config.
|
configKey: string,
|
||||||
* @param configKey The git config key to remove
|
submoduleOnly: boolean = false
|
||||||
*/
|
): Promise<void> {
|
||||||
private async removeGitConfig(configKey: string): Promise<void> {
|
if (!submoduleOnly) {
|
||||||
if (
|
if (
|
||||||
(await this.git.configExists(configKey)) &&
|
(await this.git.configExists(configKey)) &&
|
||||||
!(await this.git.tryConfigUnset(configKey))
|
!(await this.git.tryConfigUnset(configKey))
|
||||||
@@ -510,14 +414,9 @@ class GitAuthHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a git config key from all submodule configs.
|
|
||||||
* @param configKey The git config key to remove
|
|
||||||
*/
|
|
||||||
private async removeSubmoduleGitConfig(configKey: string): Promise<void> {
|
|
||||||
const pattern = regexpHelper.escape(configKey)
|
const pattern = regexpHelper.escape(configKey)
|
||||||
await this.git.submoduleForeach(
|
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}' || :"`,
|
`sh -c "git config --local --name-only --get-regexp '${pattern}' && git config --local --unset-all '${configKey}' || :"`,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
@@ -525,19 +424,30 @@ class GitAuthHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes includeIf entries that point to git-credentials-*.config files.
|
* Removes includeIf entries that point to git-credentials-*.config files.
|
||||||
|
* This handles cleanup of credentials configured by newer versions of the action.
|
||||||
* @param configPath Optional path to a specific git config file to operate on
|
* @param configPath Optional path to a specific git config file to operate on
|
||||||
* @returns Array of unique credentials config file paths that were found and removed
|
* @returns Array of unique credentials config file paths that were found and removed
|
||||||
*/
|
*/
|
||||||
private async removeIncludeIfCredentials(configPath?: string): Promise<string[]> {
|
private async removeIncludeIfCredentials(
|
||||||
|
configPath?: string
|
||||||
|
): Promise<string[]> {
|
||||||
const credentialsPaths = new Set<string>()
|
const credentialsPaths = new Set<string>()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get all includeIf.gitdir keys
|
// Get all includeIf.gitdir keys
|
||||||
const keys = await this.git.tryGetConfigKeys('^includeIf\\.gitdir:', false, configPath)
|
const keys = await this.git.tryGetConfigKeys(
|
||||||
|
'^includeIf\\.gitdir:',
|
||||||
|
false, // globalConfig?
|
||||||
|
configPath
|
||||||
|
)
|
||||||
|
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
// Get all values for this key
|
// Get all values for this key
|
||||||
const values = await this.git.tryGetConfigValues(key, false, configPath)
|
const values = await this.git.tryGetConfigValues(
|
||||||
|
key,
|
||||||
|
false, // globalConfig?
|
||||||
|
configPath
|
||||||
|
)
|
||||||
if (values.length > 0) {
|
if (values.length > 0) {
|
||||||
// Remove only values that match git-credentials-<uuid>.config pattern
|
// Remove only values that match git-credentials-<uuid>.config pattern
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
@@ -550,18 +460,16 @@ class GitAuthHelper {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Ignore errors - this is cleanup code
|
// Ignore errors - this is cleanup code
|
||||||
if (configPath) {
|
core.debug(
|
||||||
core.debug(`Error during includeIf cleanup for ${configPath}: ${err}`)
|
`Error during includeIf cleanup${configPath ? ` for ${configPath}` : ''}: ${err}`
|
||||||
} else {
|
)
|
||||||
core.debug(`Error during includeIf cleanup: ${err}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(credentialsPaths)
|
return Array.from(credentialsPaths)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests if a path matches the git-credentials-*.config pattern.
|
* Tests if a path matches the git-credentials-*.config pattern used by newer versions.
|
||||||
* @param path The path to test
|
* @param path The path to test
|
||||||
* @returns True if the path matches the credentials config pattern
|
* @returns True if the path matches the credentials config pattern
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ export interface IGitCommandManager {
|
|||||||
configKey: string,
|
configKey: string,
|
||||||
configValue: string,
|
configValue: string,
|
||||||
globalConfig?: boolean,
|
globalConfig?: boolean,
|
||||||
add?: boolean,
|
add?: boolean
|
||||||
configFile?: string
|
|
||||||
): Promise<void>
|
): Promise<void>
|
||||||
configExists(configKey: string, globalConfig?: boolean): Promise<boolean>
|
configExists(configKey: string, globalConfig?: boolean): Promise<boolean>
|
||||||
fetch(
|
fetch(
|
||||||
@@ -61,11 +60,24 @@ export interface IGitCommandManager {
|
|||||||
tagExists(pattern: string): Promise<boolean>
|
tagExists(pattern: string): Promise<boolean>
|
||||||
tryClean(): Promise<boolean>
|
tryClean(): Promise<boolean>
|
||||||
tryConfigUnset(configKey: string, globalConfig?: boolean): Promise<boolean>
|
tryConfigUnset(configKey: string, globalConfig?: boolean): Promise<boolean>
|
||||||
tryConfigUnsetValue(configKey: string, configValue: string, globalConfig?: boolean, configFile?: string): Promise<boolean>
|
tryConfigUnsetValue(
|
||||||
|
configKey: string,
|
||||||
|
configValue: string,
|
||||||
|
globalConfig?: boolean,
|
||||||
|
configFile?: string
|
||||||
|
): Promise<boolean>
|
||||||
tryDisableAutomaticGarbageCollection(): Promise<boolean>
|
tryDisableAutomaticGarbageCollection(): Promise<boolean>
|
||||||
tryGetFetchUrl(): Promise<string>
|
tryGetFetchUrl(): Promise<string>
|
||||||
tryGetConfigValues(configKey: string, globalConfig?: boolean, configFile?: string): Promise<string[]>
|
tryGetConfigValues(
|
||||||
tryGetConfigKeys(pattern: string, globalConfig?: boolean, configFile?: string): Promise<string[]>
|
configKey: string,
|
||||||
|
globalConfig?: boolean,
|
||||||
|
configFile?: string
|
||||||
|
): Promise<string[]>
|
||||||
|
tryGetConfigKeys(
|
||||||
|
pattern: string,
|
||||||
|
globalConfig?: boolean,
|
||||||
|
configFile?: string
|
||||||
|
): Promise<string[]>
|
||||||
tryReset(): Promise<boolean>
|
tryReset(): Promise<boolean>
|
||||||
version(): Promise<GitVersion>
|
version(): Promise<GitVersion>
|
||||||
}
|
}
|
||||||
@@ -228,15 +240,9 @@ class GitCommandManager {
|
|||||||
configKey: string,
|
configKey: string,
|
||||||
configValue: string,
|
configValue: string,
|
||||||
globalConfig?: boolean,
|
globalConfig?: boolean,
|
||||||
add?: boolean,
|
add?: boolean
|
||||||
configFile?: string
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const args: string[] = ['config']
|
const args: string[] = ['config', globalConfig ? '--global' : '--local']
|
||||||
if (configFile) {
|
|
||||||
args.push('--file', configFile)
|
|
||||||
} else {
|
|
||||||
args.push(globalConfig ? '--global' : '--local')
|
|
||||||
}
|
|
||||||
if (add) {
|
if (add) {
|
||||||
args.push('--add')
|
args.push('--add')
|
||||||
}
|
}
|
||||||
@@ -544,7 +550,10 @@ class GitCommandManager {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.stdout.trim().split('\n').filter(value => value.trim())
|
return output.stdout
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter(value => value.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryGetConfigKeys(
|
async tryGetConfigKeys(
|
||||||
@@ -566,7 +575,10 @@ class GitCommandManager {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.stdout.trim().split('\n').filter(key => key.trim())
|
return output.stdout
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter(key => key.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryReset(): Promise<boolean> {
|
async tryReset(): Promise<boolean> {
|
||||||
|
|||||||
Reference in New Issue
Block a user